Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype,
[mmh] / uip / popsbr.c
1 /*
2  * popsbr.c -- POP client subroutines
3  *
4  * This code is Copyright (c) 2002, by the authors of nmh.  See the
5  * COPYRIGHT file in the root directory of the nmh distribution for
6  * complete copyright information.
7  */
8
9 #include <h/mh.h>
10 #include <h/utils.h>
11
12 #ifdef CYRUS_SASL
13 # include <sasl/sasl.h>
14 # include <sasl/saslutil.h>
15 # if SASL_VERSION_FULL < 0x020125
16     /* Cyrus SASL 2.1.25 introduced the sasl_callback_ft prototype,
17        which has an explicit void parameter list, according to best
18        practice.  So we need to cast to avoid compile warnings.
19        Provide this prototype for earlier versions. */
20     typedef int (*sasl_callback_ft)();
21 # endif /* SASL_VERSION_FULL < 0x020125 */
22 #endif /* CYRUS_SASL */
23
24 #include <h/popsbr.h>
25 #include <h/signals.h>
26 #include <signal.h>
27 #include <errno.h>
28
29 #define TRM     "."
30 #define TRMLEN  (sizeof TRM - 1)
31
32 static int poprint = 0;
33 static int pophack = 0;
34
35 char response[BUFSIZ];
36
37 static FILE *input;
38 static FILE *output;
39
40 #ifdef CYRUS_SASL
41 static sasl_conn_t *conn;       /* SASL connection state */
42 static int sasl_complete = 0;   /* Has sasl authentication succeeded? */
43 static int maxoutbuf;           /* Maximum output buffer size */
44 static sasl_ssf_t sasl_ssf = 0; /* Security strength factor */
45 static int sasl_get_user(void *, int, const char **, unsigned *);
46 static int sasl_get_pass(sasl_conn_t *, void *, int, sasl_secret_t **);
47 struct pass_context {
48     char *user;
49     char *host;
50 };
51
52 static sasl_callback_t callbacks[] = {
53     { SASL_CB_USER, (sasl_callback_ft) sasl_get_user, NULL },
54 #define POP_SASL_CB_N_USER 0
55     { SASL_CB_PASS, (sasl_callback_ft) sasl_get_pass, NULL },
56 #define POP_SASL_CB_N_PASS 1
57     { SASL_CB_LOG, NULL, NULL },
58     { SASL_CB_LIST_END, NULL, NULL },
59
60 #define SASL_BUFFER_SIZE 262144
61 };
62 #else /* CYRUS_SASL */
63 # define sasl_fgetc fgetc
64 #endif /* CYRUS_SASL */
65
66 /*
67  * static prototypes
68  */
69
70 static int command(const char *, ...);
71 static int multiline(void);
72
73 #ifdef CYRUS_SASL
74 static int pop_auth_sasl(char *, char *, char *);
75 static int sasl_fgetc(FILE *);
76 #endif /* CYRUS_SASL */
77
78 static int traverse (int (*)(char *), const char *, ...);
79 static int vcommand(const char *, va_list);
80 static int sasl_getline (char *, int, FILE *);
81 static int putline (char *, FILE *);
82
83
84 #ifdef CYRUS_SASL
85 /*
86  * This function implements the AUTH command for various SASL mechanisms
87  *
88  * We do the whole SASL dialog here.  If this completes, then we've
89  * authenticated successfully and have (possibly) negotiated a security
90  * layer.
91  */
92
93 int
94 pop_auth_sasl(char *user, char *host, char *mech)
95 {
96     int result, status, sasl_capability = 0;
97     unsigned int buflen, outlen;
98     char server_mechs[256], *buf, outbuf[BUFSIZ];
99     const char *chosen_mech;
100     sasl_security_properties_t secprops;
101     struct pass_context p_context;
102     sasl_ssf_t *ssf;
103     int *moutbuf;
104
105     /*
106      * First off, we're going to send the CAPA command to see if we can
107      * even support the AUTH command, and if we do, then we'll get a
108      * list of mechanisms the server supports.  If we don't support
109      * the CAPA command, then it's unlikely that we will support
110      * SASL
111      */
112
113     if (command("CAPA") == NOTOK) {
114         snprintf(response, sizeof(response),
115                  "The POP CAPA command failed; POP server does not "
116                  "support SASL");
117         return NOTOK;
118     }
119
120     while ((status = multiline()) != DONE)
121         switch (status) {
122         case NOTOK:
123             return NOTOK;
124             break;
125         case DONE:      /* Shouldn't be possible, but just in case */
126             break;
127         case OK:
128             if (strncasecmp(response, "SASL ", 5) == 0) {
129                 /*
130                  * We've seen the SASL capability.  Grab the mech list
131                  */
132                 sasl_capability++;
133                 strncpy(server_mechs, response + 5, sizeof(server_mechs));
134             }
135             break;
136         }
137
138     if (!sasl_capability) {
139         snprintf(response, sizeof(response), "POP server does not support "
140                  "SASL");
141         return NOTOK;
142     }
143
144     /*
145      * If we received a preferred mechanism, see if the server supports it.
146      */
147
148     if (mech && stringdex(mech, server_mechs) == -1) {
149         snprintf(response, sizeof(response), "Requested SASL mech \"%s\" is "
150                  "not in list of supported mechanisms:\n%s",
151                  mech, server_mechs);
152         return NOTOK;
153     }
154
155     /*
156      * Start the SASL process.  First off, initialize the SASL library.
157      */
158
159     callbacks[POP_SASL_CB_N_USER].context = user;
160     p_context.user = user;
161     p_context.host = host;
162     callbacks[POP_SASL_CB_N_PASS].context = &p_context;
163
164     result = sasl_client_init(callbacks);
165
166     if (result != SASL_OK) {
167         snprintf(response, sizeof(response), "SASL library initialization "
168                  "failed: %s", sasl_errstring(result, NULL, NULL));
169         return NOTOK;
170     }
171
172     result = sasl_client_new("pop", host, NULL, NULL, NULL, 0, &conn);
173
174     if (result != SASL_OK) {
175         snprintf(response, sizeof(response), "SASL client initialization "
176                  "failed: %s", sasl_errstring(result, NULL, NULL));
177         return NOTOK;
178     }
179
180     /*
181      * Initialize the security properties
182      */
183
184     memset(&secprops, 0, sizeof(secprops));
185     secprops.maxbufsize = SASL_BUFFER_SIZE;
186     secprops.max_ssf = UINT_MAX;
187
188     result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
189
190     if (result != SASL_OK) {
191         snprintf(response, sizeof(response), "SASL security property "
192                  "initialization failed: %s", sasl_errdetail(conn));
193         return NOTOK;
194     }
195
196     /*
197      * Start the actual protocol.  Feed the mech list into the library
198      * and get out a possible initial challenge
199      */
200
201     result = sasl_client_start(conn,
202                                (const char *) (mech ? mech : server_mechs),
203                                NULL, (const char **) &buf,
204                                &buflen, &chosen_mech);
205
206     if (result != SASL_OK && result != SASL_CONTINUE) {
207         snprintf(response, sizeof(response), "SASL client start failed: %s",
208                  sasl_errdetail(conn));
209         return NOTOK;
210     }
211
212     if (buflen) {
213         status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
214         if (status != SASL_OK) {
215             snprintf(response, sizeof(response), "SASL base64 encode "
216                      "failed: %s", sasl_errstring(status, NULL, NULL));
217             return NOTOK;
218         }
219
220         status = command("AUTH %s %s", chosen_mech, outbuf);
221     } else
222         status = command("AUTH %s", chosen_mech);
223
224     while (result == SASL_CONTINUE) {
225         if (status == NOTOK)
226             return NOTOK;
227         
228         /*
229          * If we get a "+OK" prefix to our response, then we should
230          * exit out of this exchange now (because authenticated should
231          * have succeeded)
232          */
233         
234         if (strncmp(response, "+OK", 3) == 0)
235             break;
236         
237         /*
238          * Otherwise, make sure the server challenge is correctly formatted
239          */
240         
241         if (strncmp(response, "+ ", 2) != 0) {
242             command("*");
243             snprintf(response, sizeof(response),
244                      "Malformed authentication message from server");
245             return NOTOK;
246         }
247
248         result = sasl_decode64(response + 2, strlen(response + 2),
249                                outbuf, sizeof(outbuf), &outlen);
250         
251         if (result != SASL_OK) {
252             command("*");
253             snprintf(response, sizeof(response), "SASL base64 decode "
254                      "failed: %s", sasl_errstring(result, NULL, NULL));
255             return NOTOK;
256         }
257
258         result = sasl_client_step(conn, outbuf, outlen, NULL,
259                                   (const char **) &buf, &buflen);
260
261         if (result != SASL_OK && result != SASL_CONTINUE) {
262             command("*");
263             snprintf(response, sizeof(response), "SASL client negotiaton "
264                      "failed: %s", sasl_errdetail(conn));
265             return NOTOK;
266         }
267
268         status = sasl_encode64(buf, buflen, outbuf, sizeof(outbuf), NULL);
269
270         if (status != SASL_OK) {
271             command("*");
272             snprintf(response, sizeof(response), "SASL base64 encode "
273                      "failed: %s", sasl_errstring(status, NULL, NULL));
274             return NOTOK;
275         }
276
277         status = command(outbuf);
278     }
279
280     /*
281      * If we didn't get a positive final response, then error out
282      * (that probably means we failed an authorization check).
283      */
284
285     if (status != OK)
286         return NOTOK;
287
288     /*
289      * We _should_ be okay now.  Get a few properties now that negotiation
290      * has completed.
291      */
292
293     result = sasl_getprop(conn, SASL_MAXOUTBUF, (const void **) &moutbuf);
294
295     if (result != SASL_OK) {
296         snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated "
297                  "output buffer size: %s", sasl_errdetail(conn));
298         return NOTOK;
299     }
300
301     maxoutbuf = *moutbuf;
302
303     result = sasl_getprop(conn, SASL_SSF, (const void **) &ssf);
304
305     sasl_ssf = *ssf;
306
307     if (result != SASL_OK) {
308         snprintf(response, sizeof(response), "Cannot retrieve SASL negotiated "
309                  "security strength factor: %s", sasl_errdetail(conn));
310         return NOTOK;
311     }
312
313     /*
314      * Limit this to what we can deal with.  Shouldn't matter much because
315      * this is only outgoing data (which should be small)
316      */
317
318     if (maxoutbuf == 0 || maxoutbuf > BUFSIZ)
319         maxoutbuf = BUFSIZ;
320
321     sasl_complete = 1;
322
323     return status;
324 }
325
326 /*
327  * Callback to return the userid sent down via the user parameter
328  */
329
330 static int
331 sasl_get_user(void *context, int id, const char **result, unsigned *len)
332 {
333     char *user = (char *) context;
334
335     if (! result || id != SASL_CB_USER)
336         return SASL_BADPARAM;
337
338     *result = user;
339     if (len)
340         *len = strlen(user);
341
342     return SASL_OK;
343 }
344
345 /*
346  * Callback to return the password (we call ruserpass, which can get it
347  * out of the .netrc
348  */
349
350 static int
351 sasl_get_pass(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret)
352 {
353     struct pass_context *p_context = (struct pass_context *) context;
354     char *pass = NULL;
355     int len;
356
357     NMH_UNUSED (conn);
358
359     if (! psecret || id != SASL_CB_PASS)
360         return SASL_BADPARAM;
361
362     ruserpass(p_context->user, &(p_context->host), &pass);
363
364     len = strlen(pass);
365
366     *psecret = (sasl_secret_t *) mh_xmalloc(sizeof(sasl_secret_t) + len);
367
368     (*psecret)->len = len;
369     strcpy((char *) (*psecret)->data, pass);
370
371     return SASL_OK;
372 }
373 #endif /* CYRUS_SASL */
374
375
376 /*
377  * Split string containing proxy command into an array of arguments
378  * suitable for passing to exec. Returned array must be freed. Shouldn't
379  * be possible to call this with host set to NULL.
380  */
381 char **
382 parse_proxy(char *proxy, char *host)
383 {
384     char **pargv, **p;
385     int pargc = 2;
386     int hlen = strlen(host);
387     int plen = 1;
388     unsigned char *cur, *pro;
389     char *c;
390     
391     /* skip any initial space */
392     for (pro = (unsigned char *) proxy; isspace(*pro); pro++)
393         continue;
394     
395     /* calculate required size for argument array */
396     for (cur = pro; *cur; cur++) {
397         if (isspace(*cur) && cur[1] && !isspace(cur[1]))
398             plen++, pargc++;
399         else if (*cur == '%' && cur[1] == 'h') {
400             plen += hlen;
401             cur++;
402         } else if (!isspace(*cur))
403             plen++;
404     }
405
406    /* put together list of arguments */
407     p = pargv = mh_xmalloc(pargc * sizeof(char *));
408     c = *pargv = mh_xmalloc(plen * sizeof(char));
409     for (cur = pro; *cur; cur++) {
410         if (isspace(*cur) && cur[1] && !isspace(cur[1])) {
411             *c++ = '\0';
412             *++p = c;
413         } else if (*cur == '%' && cur[1] == 'h') {
414             strcpy (c, host);
415             c += hlen;
416             cur++;
417         } else if (!isspace(*cur))
418             *c++ = *cur;
419     }
420     *++p = NULL;
421     return pargv;
422 }
423
424 int
425 pop_init (char *host, char *port, char *user, char *pass, char *proxy,
426           int snoop, int sasl, char *mech)
427 {
428     int fd1, fd2;
429     char buffer[BUFSIZ];
430 #ifndef CYRUS_SASL
431     NMH_UNUSED (sasl);
432     NMH_UNUSED (mech);
433 #endif /* ! CYRUS_SASL */
434
435     if (proxy && *proxy) {
436        int pid;
437        int inpipe[2];     /* for reading from the server */
438        int outpipe[2];    /* for sending to the server */
439
440        /* first give up any root priviledges we may have for rpop */
441        setuid(getuid());
442
443        pipe(inpipe);
444        pipe(outpipe);
445
446        pid=fork();
447        if (pid==0) {
448            char **argv;
449            
450            /* in child */
451            close(0);  
452            close(1);
453            dup2(outpipe[0],0);  /* connect read end of connection */
454            dup2(inpipe[1], 1);  /* connect write end of connection */
455            if(inpipe[0]>1) close(inpipe[0]);
456            if(inpipe[1]>1) close(inpipe[1]);
457            if(outpipe[0]>1) close(outpipe[0]);
458            if(outpipe[1]>1) close(outpipe[1]);
459
460            /* run the proxy command */
461            argv=parse_proxy(proxy, host);
462            execvp(argv[0],argv);
463
464            perror(argv[0]);
465            close(0);
466            close(1);
467            free(*argv);
468            free(argv);
469            exit(10);
470        }
471
472        /* okay in the parent we do some stuff */
473        close(inpipe[1]);  /* child uses this */
474        close(outpipe[0]); /* child uses this */
475
476        /* we read on fd1 */
477        fd1=inpipe[0];
478        /* and write on fd2 */
479        fd2=outpipe[1];
480
481     } else {
482
483         if ((fd1 = client (host, port ? port : "pop3", response,
484                            sizeof(response), snoop)) == NOTOK) {
485             return NOTOK;
486         }
487
488         if ((fd2 = dup (fd1)) == NOTOK) {
489             char *s;
490
491             if ((s = strerror(errno)))
492                 snprintf (response, sizeof(response),
493                     "unable to dup connection descriptor: %s", s);
494             else
495                 snprintf (response, sizeof(response),
496                     "unable to dup connection descriptor: unknown error");
497             close (fd1);
498             return NOTOK;
499         }
500     }
501     if (pop_set (fd1, fd2, snoop) == NOTOK)
502         return NOTOK;
503
504     SIGNAL (SIGPIPE, SIG_IGN);
505
506     switch (sasl_getline (response, sizeof response, input)) {
507         case OK: 
508             if (poprint)
509                 fprintf (stderr, "<--- %s\n", response);
510             if (*response == '+') {
511 #  ifdef CYRUS_SASL
512                 if (sasl) {
513                     if (pop_auth_sasl(user, host, mech) != NOTOK)
514                         return OK;
515                 } else
516 #  endif /* CYRUS_SASL */
517                 if (command ("USER %s", user) != NOTOK
518                     && command ("%s %s", (pophack++, "PASS"),
519                                         pass) != NOTOK)
520                 return OK;
521             }
522             strncpy (buffer, response, sizeof(buffer));
523             command ("QUIT");
524             strncpy (response, buffer, sizeof(response));
525                                 /* and fall */
526
527         case NOTOK: 
528         case DONE: 
529             if (poprint)            
530                 fprintf (stderr, "%s\n", response);
531             fclose (input);
532             fclose (output);
533             return NOTOK;
534     }
535
536     return NOTOK;       /* NOTREACHED */
537 }
538
539 int
540 pop_set (int in, int out, int snoop)
541 {
542
543     if ((input = fdopen (in, "r")) == NULL
544             || (output = fdopen (out, "w")) == NULL) {
545         strncpy (response, "fdopen failed on connection descriptor", sizeof(response));
546         if (input)
547             fclose (input);
548         else
549             close (in);
550         close (out);
551         return NOTOK;
552     }
553
554     poprint = snoop;
555
556     return OK;
557 }
558
559
560 int
561 pop_fd (char *in, int inlen, char *out, int outlen)
562 {
563     snprintf (in, inlen, "%d", fileno (input));
564     snprintf (out, outlen, "%d", fileno (output));
565     return OK;
566 }
567
568
569 /*
570  * Find out number of messages available
571  * and their total size.
572  */
573
574 int
575 pop_stat (int *nmsgs, int *nbytes)
576 {
577
578     if (command ("STAT") == NOTOK)
579         return NOTOK;
580
581     *nmsgs = *nbytes = 0;
582     sscanf (response, "+OK %d %d", nmsgs, nbytes);
583
584     return OK;
585 }
586
587
588 int
589 pop_list (int msgno, int *nmsgs, int *msgs, int *bytes)
590 {
591     int i;
592     int *ids = NULL;
593
594     if (msgno) {
595         if (command ("LIST %d", msgno) == NOTOK)
596             return NOTOK;
597         *msgs = *bytes = 0;
598         if (ids) {
599             *ids = 0;
600             sscanf (response, "+OK %d %d %d", msgs, bytes, ids);
601         }
602         else
603             sscanf (response, "+OK %d %d", msgs, bytes);
604         return OK;
605     }
606
607     if (command ("LIST") == NOTOK)
608         return NOTOK;
609
610     for (i = 0; i < *nmsgs; i++)
611         switch (multiline ()) {
612             case NOTOK: 
613                 return NOTOK;
614             case DONE: 
615                 *nmsgs = ++i;
616                 return OK;
617             case OK: 
618                 *msgs = *bytes = 0;
619                 if (ids) {
620                     *ids = 0;
621                     sscanf (response, "%d %d %d",
622                             msgs++, bytes++, ids++);
623                 }
624                 else
625                     sscanf (response, "%d %d", msgs++, bytes++);
626                 break;
627         }
628     for (;;)
629         switch (multiline ()) {
630             case NOTOK: 
631                 return NOTOK;
632             case DONE: 
633                 return OK;
634             case OK: 
635                 break;
636         }
637 }
638
639
640 int
641 pop_retr (int msgno, int (*action)(char *))
642 {
643     return traverse (action, "RETR %d", msgno);
644 }
645
646
647 static int
648 traverse (int (*action)(char *), const char *fmt, ...)
649 {
650     int result;
651     va_list ap;
652     char buffer[sizeof(response)];
653
654     va_start(ap, fmt);
655     result = vcommand (fmt, ap);
656     va_end(ap);
657
658     if (result == NOTOK)
659         return NOTOK;
660     strncpy (buffer, response, sizeof(buffer));
661
662     for (;;)
663         switch (multiline ()) {
664             case NOTOK: 
665                 return NOTOK;
666
667             case DONE: 
668                 strncpy (response, buffer, sizeof(response));
669                 return OK;
670
671             case OK: 
672                 (*action) (response);
673                 break;
674         }
675 }
676
677
678 int
679 pop_dele (int msgno)
680 {
681     return command ("DELE %d", msgno);
682 }
683
684
685 int
686 pop_noop (void)
687 {
688     return command ("NOOP");
689 }
690
691
692 int
693 pop_rset (void)
694 {
695     return command ("RSET");
696 }
697
698
699 int
700 pop_top (int msgno, int lines, int (*action)(char *))
701 {
702     return traverse (action, "TOP %d %d", msgno, lines);
703 }
704
705
706 int
707 pop_quit (void)
708 {
709     int i;
710
711     i = command ("QUIT");
712     pop_done ();
713
714     return i;
715 }
716
717
718 int
719 pop_done (void)
720 {
721 #ifdef CYRUS_SASL
722     if (conn)
723         sasl_dispose(&conn);
724 #endif /* CYRUS_SASL */
725     fclose (input);
726     fclose (output);
727
728     return OK;
729 }
730
731
732 int
733 command(const char *fmt, ...)
734 {
735     va_list ap;
736     int result;
737
738     va_start(ap, fmt);
739     result = vcommand(fmt, ap);
740     va_end(ap);
741
742     return result;
743 }
744
745
746 static int
747 vcommand (const char *fmt, va_list ap)
748 {
749     char *cp, buffer[BUFSIZ];
750
751     vsnprintf (buffer, sizeof(buffer), fmt, ap);
752     if (poprint) {
753 #ifdef CYRUS_SASL
754         if (sasl_ssf)
755             fprintf(stderr, "(encrypted) ");
756 #endif /* CYRUS_SASL */
757         if (pophack) {
758             if ((cp = strchr (buffer, ' ')))
759                 *cp = 0;
760             fprintf (stderr, "---> %s ********\n", buffer);
761             if (cp)
762                 *cp = ' ';
763             pophack = 0;
764         }
765         else
766             fprintf (stderr, "---> %s\n", buffer);
767     }
768
769     if (putline (buffer, output) == NOTOK)
770         return NOTOK;
771
772 #ifdef CYRUS_SASL
773     if (poprint && sasl_ssf)
774         fprintf(stderr, "(decrypted) ");
775 #endif /* CYRUS_SASL */
776
777     switch (sasl_getline (response, sizeof response, input)) {
778         case OK: 
779             if (poprint)
780                 fprintf (stderr, "<--- %s\n", response);
781             return (*response == '+' ? OK : NOTOK);
782
783         case NOTOK: 
784         case DONE: 
785             if (poprint)            
786                 fprintf (stderr, "%s\n", response);
787             return NOTOK;
788     }
789
790     return NOTOK;       /* NOTREACHED */
791 }
792
793
794 int
795 multiline (void)
796 {
797     char buffer[BUFSIZ + TRMLEN];
798
799     if (sasl_getline (buffer, sizeof buffer, input) != OK)
800         return NOTOK;
801 #ifdef DEBUG
802     if (poprint) {
803 #ifdef CYRUS_SASL
804         if (sasl_ssf)
805             fprintf(stderr, "(decrypted) ");
806 #endif /* CYRUS_SASL */
807         fprintf (stderr, "<--- %s\n", response);
808     }
809 #endif /* DEBUG */
810     if (strncmp (buffer, TRM, TRMLEN) == 0) {
811         if (buffer[TRMLEN] == 0)
812             return DONE;
813         else
814             strncpy (response, buffer + TRMLEN, sizeof(response));
815     }
816     else
817         strncpy (response, buffer, sizeof(response));
818
819     return OK;
820 }
821
822 /*
823  * Note that these functions have been modified to deal with layer encryption
824  * in the SASL case
825  */
826
827 static int
828 sasl_getline (char *s, int n, FILE *iop)
829 {
830     int c = -2;
831     char *p;
832
833     p = s;
834     while (--n > 0 && (c = sasl_fgetc (iop)) != EOF && c != -2) 
835         if ((*p++ = c) == '\n')
836             break;
837     if (c == -2)
838         return NOTOK;
839     if (ferror (iop) && c != EOF) {
840         strncpy (response, "error on connection", sizeof(response));
841         return NOTOK;
842     }
843     if (c == EOF && p == s) {
844         strncpy (response, "connection closed by foreign host", sizeof(response));
845         return DONE;
846     }
847     *p = 0;
848     if (*--p == '\n')
849         *p = 0;
850     if (*--p == '\r')
851         *p = 0;
852
853     return OK;
854 }
855
856
857 static int
858 putline (char *s, FILE *iop)
859 {
860 #ifdef CYRUS_SASL
861     char outbuf[BUFSIZ], *buf;
862     int result;
863     unsigned int buflen;
864
865     if (!sasl_complete) {
866 #endif /* CYRUS_SASL */
867         fprintf (iop, "%s\r\n", s);
868 #ifdef CYRUS_SASL
869     } else {
870         /*
871          * Build an output buffer, encrypt it using sasl_encode, and
872          * squirt out the results.
873          */
874         strncpy(outbuf, s, sizeof(outbuf) - 3);
875         outbuf[sizeof(outbuf) - 3] = '\0';   /* Just in case */
876         strcat(outbuf, "\r\n");
877
878         result = sasl_encode(conn, outbuf, strlen(outbuf),
879                              (const char **) &buf, &buflen);
880
881         if (result != SASL_OK) {
882             snprintf(response, sizeof(response), "SASL encoding error: %s",
883                      sasl_errdetail(conn));
884             return NOTOK;
885         }
886
887         fwrite(buf, buflen, 1, iop);
888     }
889 #endif /* CYRUS_SASL */
890
891     fflush (iop);
892     if (ferror (iop)) {
893         strncpy (response, "lost connection", sizeof(response));
894         return NOTOK;
895     }
896
897     return OK;
898 }
899
900 #ifdef CYRUS_SASL
901 /*
902  * Okay, our little fgetc replacement.  Hopefully this is a little more
903  * efficient than the last one.
904  */
905 static int
906 sasl_fgetc(FILE *f)
907 {
908     static unsigned char *buffer = NULL, *ptr;
909     static unsigned int size = 0;
910     static int cnt = 0;
911     unsigned int retbufsize = 0;
912     int cc, result;
913     char *retbuf, tmpbuf[SASL_BUFFER_SIZE];
914
915     /*
916      * If we have some leftover data, return that
917      */
918
919     if (cnt) {
920         cnt--;
921         return (int) *ptr++;
922     }
923
924     /*
925      * Otherwise, fill our buffer until we have some data to return.
926      */
927
928     while (retbufsize == 0) {
929
930         cc = read(fileno(f), tmpbuf, sizeof(tmpbuf));
931
932         if (cc == 0)
933             return EOF;
934
935         if (cc < 0) {
936             snprintf(response, sizeof(response), "Error during read from "
937                      "network: %s", strerror(errno));
938             return -2;
939         }
940
941         /*
942          * We're not allowed to call sasl_decode until sasl_complete is
943          * true, so we do these gyrations ...
944          */
945         
946         if (!sasl_complete) {
947
948             retbuf = tmpbuf;
949             retbufsize = cc;
950
951         } else {
952
953             result = sasl_decode(conn, tmpbuf, cc,
954                                  (const char **) &retbuf, &retbufsize);
955
956             if (result != SASL_OK) {
957                 snprintf(response, sizeof(response), "Error during SASL "
958                          "decoding: %s", sasl_errdetail(conn));
959                 return -2;
960             }
961         }
962     }
963
964     if (retbufsize > size) {
965         buffer = mh_xrealloc(buffer, retbufsize);
966         size = retbufsize;
967     }
968
969     memcpy(buffer, retbuf, retbufsize);
970     ptr = buffer + 1;
971     cnt = retbufsize - 1;
972
973     return (int) buffer[0];
974 }
975 #endif /* CYRUS_SASL */