SXEmacs, meet Dbus. Dbus, meet SXEmacs
[sxemacs] / lib-src / pop.c
1 /* pop.c: client routines for talking to a POP3-protocol post-office server
2    Copyright (C) 1991, 1993, 1996, 1997, 1999, 2001, 2002, 2003, 2004,
3                  2005, 2006, 2007  Free Software Foundation, Inc.
4    Written by Jonathan Kamens, jik@security.ov.com.
5
6 This file is part of SXEmacs.
7
8 SXEmacs 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 3 of the License, or
11 (at your option) any later version.
12
13 SXEmacs 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, see <http://www.gnu.org/licenses/>. */
20
21 #ifdef HAVE_CONFIG_H
22 #define NO_SHORTNAMES   /* Tell config not to load remap.h */
23 #include <config.h>
24 #else
25 #define MAIL_USE_POP
26 #endif
27
28 #ifdef MAIL_USE_POP
29
30 #include <sys/types.h>
31 #include <netinet/in.h>
32 #include <sys/socket.h>
33 #define RECV(s,buf,len,flags) read(s,buf,len)
34 #define SEND(s,buf,len,flags) write(s,buf,len)
35 #define CLOSESOCKET(s) close(s)
36 #include <pop.h>
37
38 #ifdef sun
39 #include <malloc.h>
40 #endif /* sun */
41
42 #ifdef HESIOD
43 #include <hesiod.h>
44 /*
45  * It really shouldn't be necessary to put this declaration here, but
46  * the version of hesiod.h that Athena has installed in release 7.2
47  * doesn't declare this function; I don't know if the 7.3 version of
48  * hesiod.h does.
49  */
50 extern struct servent *hes_getservbyname (/* char *, char * */);
51 #endif
52
53 #include <stdlib.h>
54 #include <pwd.h>
55 #include <netdb.h>
56 #include <errno.h>
57 #include <stdio.h>
58 #ifdef STDC_HEADERS
59 #include <string.h>
60 #define index strchr
61 #endif
62 #ifdef HAVE_UNISTD_H
63 #include <unistd.h>
64 #endif
65
66 #if defined WITH_KERBEROS && defined HAVE_KERBEROS
67 # if defined HAVE_KRB5_KRB5_H
68 #  include <krb/krb5.h>
69 # elif defined HAVE_KRB5_H
70 #  include <krb5.h>
71 # elif defined HAVE_KRB_KRB_H
72 #  include <krb/krb.h>
73 # elif defined HAVE_KRB_H
74 #  include <krb.h>
75 # elif defined HAVE_KERBEROS_KRB_H
76 #  include <kerberos/krb.h>
77 # elif defined HAVE_KERBEROSIV_KRB_H
78 #  include <kerberosIV/krb.h>
79 # else
80 #  error "What are you doing here?"
81 # endif
82 # if !defined HAVE_KERBEROS5 && defined HAVE_KRB_COM_ERR_H
83 #  include <krb/com_err.h>
84 # elif !defined HAVE_KERBEROS5 && defined HAVE_KERBEROSIV_KRB_ERR_H
85 #  include <kerberosIV/krb_err.h>
86 # elif !defined HAVE_KERBEROS5 && defined COM_ERR_H
87 #  include <com_err.h>
88 # endif
89 #endif /* KERBEROS */
90
91 #if defined WITH_KERBEROS && defined HAVE_KERBEROS
92 #if !defined HAVE_KERBEROS5
93 extern int krb_sendauth (/* long, int, KTEXT, char *, char *, char *,
94                             u_long, MSG_DAT *, CREDENTIALS *, Key_schedule,
95                             struct sockaddr_in *, struct sockaddr_in *,
96                             char * */);
97 extern char *krb_realmofhost (/* char * */);
98 #endif /* ! HAVE_KERBEROS5 */
99 #endif /* WITH_KERBEROS && HAVE_KERBEROS */
100
101 #if !defined(HAVE_H_ERRNO) || !defined(HAVE_CONFIG_H)
102 extern int h_errno;
103 #endif
104
105 #ifndef __P
106 # ifdef __STDC__
107 #  define __P(a) a
108 # else
109 #  define __P(a) ()
110 # endif /* __STDC__ */
111 #endif /* ! __P */
112
113 static int socket_connection __P((char *, int));
114 static int pop_getline __P((popserver, char **));
115 static int sendline __P((popserver, char *));
116 static int fullwrite __P((int, char *, int));
117 static int getok __P((popserver));
118 #if 0
119 static int gettermination __P((popserver));
120 #endif
121 static void pop_trash __P((popserver));
122 static char *find_crlf __P((char *, int));
123
124 #define ERROR_MAX 160           /* a pretty arbitrary size, but needs
125                                    to be bigger than the original
126                                    value of 80 */
127 #define POP_PORT 110
128 #define KPOP_PORT 1109
129 #define POP_SERVICE "pop3"      /* we don't want the POP2 port! */
130 #ifdef KERBEROS
131 #define KPOP_SERVICE "kpop"     /* never used: look for 20060515 to see why */
132 #endif
133
134 char pop_error[ERROR_MAX];
135 int pop_debug = 0;
136
137 #ifndef min
138 #define min(a,b) (((a) < (b)) ? (a) : (b))
139 #endif
140
141 #define xstrncpy(d_,s_,l_)                      \
142         do {                                    \
143                 char* dst_=d_;                  \
144                 dst_[0]='\0';                   \
145                 strncat((dst_),(s_),(l_)-1);    \
146         } while(0)
147
148 /*
149  * Function: pop_open (char *host, char *username, char *password,
150  *                     int flags)
151  *
152  * Purpose: Establishes a connection with a post-office server, and
153  *      completes the authorization portion of the session.
154  *
155  * Arguments:
156  *      host    The server host with which the connection should be
157  *              established.  Optional.  If omitted, internal
158  *              heuristics will be used to determine the server host,
159  *              if possible.
160  *      username
161  *              The username of the mail-drop to access.  Optional.
162  *              If omitted, internal heuristics will be used to
163  *              determine the username, if possible.
164  *      password
165  *              The password to use for authorization.  If omitted,
166  *              internal heuristics will be used to determine the
167  *              password, if possible.
168  *      flags   A bit mask containing flags controlling certain
169  *              functions of the routine.  Valid flags are defined in
170  *              the file pop.h
171  *
172  * Return value: Upon successful establishment of a connection, a
173  *      non-null popserver will be returned.  Otherwise, null will be
174  *      returned, and the string variable pop_error will contain an
175  *      explanation of the error.
176  */
177 popserver
178 pop_open (host, username, password, flags)
179      char *host;
180      char *username;
181      char *password;
182      int flags;
183 {
184         int sock;
185         int sz;
186         popserver server;
187
188         /* Determine the user name */
189         if (! username) {
190                 username = getenv ("USER");
191                 if (! (username && *username)) {
192                         username = getlogin ();
193                         if (! (username && *username)) {
194                                 struct passwd *passwd;
195                                 passwd = getpwuid (getuid ());
196                                 if (passwd && passwd->pw_name &&
197                                     *passwd->pw_name) {
198                                         username = passwd->pw_name;
199                                 } else {
200                                         strcpy (pop_error,
201                                                 "Could not determine username");
202                                         return (0);
203                                 }
204                         }
205                 }
206         }
207
208         /*
209          *  Determine the mail host.
210          */
211
212         if (! host) {
213                 host = getenv ("MAILHOST");
214         }
215
216 #ifdef HESIOD
217         if ((! host) && (! (flags & POP_NO_HESIOD))) {
218                 struct hes_postoffice *office;
219                 office = hes_getmailhost (username);
220                 if (office && office->po_type && (! strcmp (office->po_type, "POP"))
221                     && office->po_name && *office->po_name && office->po_host
222                     && *office->po_host) {
223                         host = office->po_host;
224                         username = office->po_name;
225                 }
226         }
227 #endif
228
229 #ifdef MAILHOST
230         if (! host) {
231                 host = MAILHOST;
232         }
233 #endif
234
235         if (! host) {
236                 strcpy (pop_error, "Could not determine POP server");
237                 return (0);
238         }
239
240         /* Determine the password */
241 #if defined WITH_KERBEROS && defined HAVE_KERBEROS
242 #define DONT_NEED_PASSWORD (! (flags & POP_NO_KERBEROS))
243 #else
244 #define DONT_NEED_PASSWORD 0
245 #endif
246
247         if ((! password) && (! DONT_NEED_PASSWORD)) {
248                 if (! (flags & POP_NO_GETPASS)) {
249                         password = getpass ("Enter POP password:");
250                 }
251                 if (! password) {
252                         strcpy (pop_error, "Could not determine POP password");
253                         return (0);
254                 }
255         }
256         if (password)                   /* always true, detected 20060515 */
257                 flags |= POP_NO_KERBEROS;
258         else
259                 password = username;    /* dead code, detected 20060515 */
260         /** "kpop" service is  never used: look for 20060515 to see why **/
261
262         sock = socket_connection (host, flags);
263         if (sock == -1)
264                 return (0);
265
266         server = (popserver) malloc (sizeof (struct _popserver));
267         if (! server) {
268                 strcpy (pop_error, "Out of memory in pop_open");
269                 return (0);
270         }
271         server->buffer = (char *) malloc (GETLINE_MIN);
272         if (! server->buffer) {
273                 strcpy (pop_error, "Out of memory in pop_open");
274                 free ((char *) server);
275                 return (0);
276         }
277
278         server->file = sock;
279         server->data = 0;
280         server->buffer_index = 0;
281         server->buffer_size = GETLINE_MIN;
282         server->in_multi = 0;
283         server->trash_started = 0;
284
285         if (getok (server))
286                 return (0);
287
288         /*
289          * I really shouldn't use the pop_error variable like this, but....
290          */
291         if (strlen (username) > ERROR_MAX - 6) {
292                 pop_close (server);
293                 strcpy (pop_error,
294                         "Username too long; "
295                         "recompile pop.c with larger ERROR_MAX");
296                 return (0);
297         }
298         sz = snprintf (pop_error, sizeof(pop_error), "USER %s", username);
299         assert(sz>=0 && sz<sizeof(pop_error));
300
301         if (sendline (server, pop_error) || getok (server)) {
302                 return (0);
303         }
304
305         if (strlen (password) > ERROR_MAX - 6) {
306                 pop_close (server);
307                 strcpy (pop_error,
308                         "Password too long; "
309                         "recompile pop.c with larger ERROR_MAX");
310                 return (0);
311         }
312         sz = snprintf (pop_error, sizeof(pop_error),
313                        "PASS %s", password);
314         assert(sz>=0 && sz<sizeof(pop_error));
315
316         if (sendline (server, pop_error) || getok (server)) {
317                 return (0);
318         }
319
320         return (server);
321 }
322
323 /*
324  * Function: pop_stat
325  *
326  * Purpose: Issue the STAT command to the server and return (in the
327  *      value parameters) the number of messages in the maildrop and
328  *      the total size of the maildrop.
329  *
330  * Return value: 0 on success, or non-zero with an error in pop_error
331  *      in failure.
332  *
333  * Side effects: On failure, may make further operations on the
334  *      connection impossible.
335  */
336 int
337 pop_stat (server, count, size)
338      popserver server;
339      int *count;
340      int *size;
341 {
342         char *fromserver;
343
344         if (server->in_multi) {
345                 strcpy (pop_error, "In multi-line query in pop_stat");
346                 return (-1);
347         }
348
349         if (sendline (server, "STAT") ||
350             (pop_getline (server, &fromserver) < 0))
351                 return (-1);
352
353         if (strncmp (fromserver, "+OK ", 4)) {
354                 if (0 == strncmp (fromserver, "-ERR", 4)) {
355                         xstrncpy (pop_error, fromserver, ERROR_MAX);
356                 } else {
357                         strcpy (pop_error,
358                                 "Unexpected response from POP "
359                                 "server in pop_stat");
360                         pop_trash (server);
361                 }
362                 return (-1);
363         }
364
365         *count = atoi (&fromserver[4]);
366
367         fromserver = index (&fromserver[4], ' ');
368         if (! fromserver) {
369                 strcpy (pop_error,
370                         "Badly formatted response from server in pop_stat");
371                 pop_trash (server);
372                 return (-1);
373         }
374
375         *size = atoi (fromserver + 1);
376
377         return (0);
378 }
379
380 /*
381  * Function: pop_list
382  *
383  * Purpose: Performs the POP "list" command and returns (in value
384  *      parameters) two malloc'd zero-terminated arrays -- one of
385  *      message IDs, and a parallel one of sizes.
386  *
387  * Arguments:
388  *      server  The pop connection to talk to.
389  *      message The number of the one message about which to get
390  *              information, or 0 to get information about all
391  *              messages.
392  *
393  * Return value: 0 on success, non-zero with error in pop_error on
394  *      failure.
395  *
396  * Side effects: On failure, may make further operations on the
397  *      connection impossible.
398  */
399 int
400 pop_list (server, message, IDs, sizes)
401      popserver server;
402      int message;
403      int **IDs;
404      int **sizes;
405 {
406         int how_many, i;
407         char *fromserver;
408
409         if (server->in_multi) {
410                 strcpy (pop_error, "In multi-line query in pop_list");
411                 return (-1);
412         }
413
414         if (message) {
415                 how_many = 1;
416         } else {
417                 int count, size;
418                 if (pop_stat (server, &count, &size))
419                         return (-1);
420                 how_many = count;
421         }
422
423         *IDs = (int *) malloc ((how_many + 1) * sizeof (int));
424         *sizes = (int *) malloc ((how_many + 1) * sizeof (int));
425         if (! (*IDs && *sizes)) {
426                 strcpy (pop_error, "Out of memory in pop_list");
427                 return (-1);
428         }
429
430         if (message) {
431                 sz = snprintf (pop_error, sizeof(pop_error), "LIST %d", message);
432                 assert(sz>=0 && sz<sizeof(pop_error));
433                 if (sendline (server, pop_error)) {
434                         free ((char *) *IDs);
435                         free ((char *) *sizes);
436                         return (-1);
437                 }
438                 if (pop_getline (server, &fromserver) < 0) {
439                         free ((char *) *IDs);
440                         free ((char *) *sizes);
441                         return (-1);
442                 }
443                 if (strncmp (fromserver, "+OK ", 4)) {
444                         if (! strncmp (fromserver, "-ERR", 4)) {
445                                 xstrncpy (pop_error, fromserver, ERROR_MAX);
446                         } else {
447                                 strcpy (pop_error,
448                                         "Unexpected response from "
449                                         "server in pop_list");
450                                 pop_trash (server);
451                         }
452                         free ((char *) *IDs);
453                         free ((char *) *sizes);
454                         return (-1);
455                 }
456                 (*IDs)[0] = atoi (&fromserver[4]);
457                 fromserver = index (&fromserver[4], ' ');
458                 if (! fromserver) {
459                         strcpy (pop_error,
460                                 "Badly formatted response from "
461                                 "server in pop_list");
462                         pop_trash (server);
463                         free ((char *) *IDs);
464                         free ((char *) *sizes);
465                         return (-1);
466                 }
467                 (*sizes)[0] = atoi (fromserver);
468                 (*IDs)[1] = (*sizes)[1] = 0;
469                 return (0);
470         } else {
471                 if (pop_multi_first (server, "LIST", &fromserver)) {
472                         free ((char *) *IDs);
473                         free ((char *) *sizes);
474                         return (-1);
475                 }
476                 for (i = 0; i < how_many; i++) {
477                         if (pop_multi_next (server, &fromserver) <= 0) {
478                                 free ((char *) *IDs);
479                                 free ((char *) *sizes);
480                                 return (-1);
481                         }
482                         (*IDs)[i] = atoi (fromserver);
483                         fromserver = index (fromserver, ' ');
484                         if (! fromserver) {
485                                 strcpy (pop_error,
486                                         "Badly formatted response from "
487                                         "server in pop_list");
488                                 free ((char *) *IDs);
489                                 free ((char *) *sizes);
490                                 pop_trash (server);
491                                 return (-1);
492                         }
493                         (*sizes)[i] = atoi (fromserver);
494                 }
495                 if (pop_multi_next (server, &fromserver) < 0) {
496                         free ((char *) *IDs);
497                         free ((char *) *sizes);
498                         return (-1);
499                 } else if (fromserver) {
500                         strcpy (pop_error,
501                                 "Too many response lines from "
502                                 "server in pop_list");
503                         free ((char *) *IDs);
504                         free ((char *) *sizes);
505                         return (-1);
506                 }
507                 (*IDs)[i] = (*sizes)[i] = 0;
508                 return (0);
509         }
510 }
511
512 /*
513  * Function: pop_retrieve
514  *
515  * Purpose: Retrieve a specified message from the maildrop.
516  *
517  * Arguments:
518  *      server  The server to retrieve from.
519  *      message The message number to retrieve.
520  *      markfrom
521  *              If true, then mark the string "From " at the beginning
522  *              of lines with '>'.
523  *      msg_buf Output parameter to which a buffer containing the
524  *              message is assigned.
525  *
526  * Return value: The number of bytes in msg_buf, which may contain
527  *      embedded nulls, not including its final null, or -1 on error
528  *      with pop_error set.
529  *
530  * Side effects: May kill connection on error.
531  */
532 int
533 pop_retrieve (server, message, markfrom, msg_buf)
534      popserver server;
535      int message;
536      int markfrom;
537      char **msg_buf;
538 {
539         int *IDs, *sizes, bufsize, fromcount = 0, cp = 0;
540         char *ptr, *fromserver;
541         int ret;
542
543         if (server->in_multi) {
544                 strcpy (pop_error, "In multi-line query in pop_retrieve");
545                 return (-1);
546         }
547
548         if (pop_list (server, message, &IDs, &sizes))
549                 return (-1);
550
551         if (pop_retrieve_first (server, message, &fromserver)) {
552                 return (-1);
553         }
554
555         /*
556          * The "5" below is an arbitrary constant -- I assume that if
557          * there are "From" lines in the text to be marked, there
558          * probably won't be more than 5 of them.  If there are, I
559          * allocate more space for them below.
560          */
561         bufsize = sizes[0] + (markfrom ? 5 : 0);
562         ptr = (char *)malloc (bufsize);
563         free ((char *) IDs);
564         free ((char *) sizes);
565
566         if (! ptr) {
567                 strcpy (pop_error, "Out of memory in pop_retrieve");
568                 pop_retrieve_flush (server);
569                 return (-1);
570         }
571
572         while ((ret = pop_retrieve_next (server, &fromserver)) >= 0) {
573                 if (! fromserver) {
574                         ptr[cp] = '\0';
575                         *msg_buf = ptr;
576                         return (cp);
577                 }
578                 if (markfrom && fromserver[0] == 'F' && fromserver[1] == 'r' &&
579                     fromserver[2] == 'o' && fromserver[3] == 'm' &&
580                     fromserver[4] == ' ') {
581                         if (++fromcount == 5) {
582                                 bufsize += 5;
583                                 ptr = (char *)realloc (ptr, bufsize);
584                                 if (! ptr) {
585                                         strcpy (pop_error, "Out of memory "
586                                                 "in pop_retrieve");
587                                         pop_retrieve_flush (server);
588                                         return (-1);
589                                 }
590                                 fromcount = 0;
591                         }
592                         ptr[cp++] = '>';
593                 }
594                 bcopy (fromserver, &ptr[cp], ret);
595                 cp += ret;
596                 ptr[cp++] = '\n';
597         }
598
599         free (ptr);
600         return (-1);
601 }
602
603 int
604 pop_retrieve_first (server, message, response)
605      popserver server;
606      int message;
607      char **response;
608 {
609         int sz = snprintf (pop_error, sizeof(pop_error), "RETR %d", message);
610         assert(sz>=0 && sz<sizeof(pop_error));
611         return (pop_multi_first (server, pop_error, response));
612 }
613
614 /*
615   Returns a negative number on error, 0 to indicate that the data has
616   all been read (i.e., the server has returned a "." termination
617   line), or a positive number indicating the number of bytes in the
618   returned buffer (which is null-terminated and may contain embedded
619   nulls, but the returned bytecount doesn't include the final null).
620   */
621
622 int
623 pop_retrieve_next (server, line)
624      popserver server;
625      char **line;
626 {
627         return (pop_multi_next (server, line));
628 }
629
630 int
631 pop_retrieve_flush (server)
632      popserver server;
633 {
634         return (pop_multi_flush (server));
635 }
636
637 int
638 pop_top_first (server, message, lines, response)
639      popserver server;
640      int message, lines;
641      char **response;
642 {
643         int sz = snprintf (pop_error, sizeof(pop_error),
644                            "TOP %d %d", message, lines);
645         assert(sz>=0 && sz<sizeof(pop_error));
646         return (pop_multi_first (server, pop_error, response));
647 }
648
649 /*
650   Returns a negative number on error, 0 to indicate that the data has
651   all been read (i.e., the server has returned a "." termination
652   line), or a positive number indicating the number of bytes in the
653   returned buffer (which is null-terminated and may contain embedded
654   nulls, but the returned bytecount doesn't include the final null).
655   */
656
657 int
658 pop_top_next (server, line)
659      popserver server;
660      char **line;
661 {
662         return (pop_multi_next (server, line));
663 }
664
665 int
666 pop_top_flush (server)
667      popserver server;
668 {
669   return (pop_multi_flush (server));
670 }
671
672 int
673 pop_multi_first (server, command, response)
674      popserver server;
675      char *command;
676      char **response;
677 {
678         if (server->in_multi) {
679                 strcpy (pop_error,
680                         "Already in multi-line query in pop_multi_first");
681                 return (-1);
682         }
683
684         if (sendline (server, command) ||
685             (pop_getline (server, response) < 0)) {
686                 return (-1);
687         }
688
689         if (0 == strncmp (*response, "-ERR", 4)) {
690                 xstrncpy (pop_error, *response, ERROR_MAX);
691                 return (-1);
692         } else if (0 == strncmp (*response, "+OK", 3)) {
693                 for (*response += 3; **response == ' ';
694                      (*response)++) /* empty */;
695                 server->in_multi = 1;
696                 return (0);
697         } else {
698                 strcpy (pop_error,
699                         "Unexpected response from "
700                         "server in pop_multi_first");
701                 return (-1);
702         }
703 }
704
705 /*
706   Read the next line of data from SERVER and place a pointer to it
707   into LINE.  Return -1 on error, 0 if there are no more lines to read
708   (i.e., the server has returned a line containing only "."), or a
709   positive number indicating the number of bytes in the LINE buffer
710   (not including the final null).  The data in that buffer may contain
711   embedded nulls, but does not contain the final CRLF. When returning
712   0, LINE is set to null. */
713
714 int
715 pop_multi_next (server, line)
716      popserver server;
717      char **line;
718 {
719         char *fromserver;
720         int ret;
721
722         if (! server->in_multi) {
723                 strcpy (pop_error, "Not in multi-line query in pop_multi_next");
724                 return (-1);
725         }
726
727         if ((ret = pop_getline (server, &fromserver)) < 0) {
728                 return (-1);
729         }
730
731         if (fromserver[0] == '.') {
732                 if (! fromserver[1]) {
733                         *line = 0;
734                         server->in_multi = 0;
735                         return (0);
736                 } else {
737                         *line = fromserver + 1;
738                         return (ret - 1);
739                 }
740         } else {
741                 *line = fromserver;
742                 return (ret);
743         }
744 }
745
746 int
747 pop_multi_flush (server)
748      popserver server;
749 {
750         char *line;
751         int ret;
752
753         if (! server->in_multi) {
754                 return (0);
755         }
756
757         while ((ret = pop_multi_next (server, &line))) {
758                 if (ret < 0)
759                         return (-1);
760         }
761
762         return (0);
763 }
764
765 /* Function: pop_delete
766  *
767  * Purpose: Delete a specified message.
768  *
769  * Arguments:
770  *      server  Server from which to delete the message.
771  *      message Message to delete.
772  *
773  * Return value: 0 on success, non-zero with error in pop_error
774  *      otherwise.
775  */
776 int
777 pop_delete (server, message)
778      popserver server;
779      int message;
780 {
781         int sz;
782
783         if (server->in_multi) {
784                 strcpy (pop_error, "In multi-line query in pop_delete");
785                 return (-1);
786         }
787
788         sz = snprintf (pop_error, sizeof(pop_error),
789                        "DELE %d", message);
790         assert(sz>=0 && sz<sizeof(pop_error));
791
792         if (sendline (server, pop_error) || getok (server))
793                 return (-1);
794
795         return (0);
796 }
797
798 /*
799  * Function: pop_noop
800  *
801  * Purpose: Send a noop command to the server.
802  *
803  * Argument:
804  *      server  The server to send to.
805  *
806  * Return value: 0 on success, non-zero with error in pop_error
807  *      otherwise.
808  *
809  * Side effects: Closes connection on error.
810  */
811 int
812 pop_noop (server)
813      popserver server;
814 {
815         if (server->in_multi) {
816                 strcpy (pop_error, "In multi-line query in pop_noop");
817                 return (-1);
818         }
819
820         if (sendline (server, "NOOP") || getok (server))
821                 return (-1);
822
823         return (0);
824 }
825
826 /*
827  * Function: pop_last
828  *
829  * Purpose: Find out the highest seen message from the server.
830  *
831  * Arguments:
832  *      server  The server.
833  *
834  * Return value: If successful, the highest seen message, which is
835  *      greater than or equal to 0.  Otherwise, a negative number with
836  *      the error explained in pop_error.
837  *
838  * Side effects: Closes the connection on error.
839  */
840 int
841 pop_last (server)
842      popserver server;
843 {
844         char *fromserver;
845
846         if (server->in_multi) {
847                 strcpy (pop_error, "In multi-line query in pop_last");
848                 return (-1);
849         }
850
851         if (sendline (server, "LAST"))
852                 return (-1);
853
854         if (pop_getline (server, &fromserver) < 0)
855                 return (-1);
856
857         if (! strncmp (fromserver, "-ERR", 4)) {
858                 xstrncpy (pop_error, fromserver, ERROR_MAX);
859                 return (-1);
860         } else if (strncmp (fromserver, "+OK ", 4)) {
861                 strcpy (pop_error, "Unexpected response from server in pop_last");
862                 pop_trash (server);
863                 return (-1);
864         } else {
865                 return (atoi (&fromserver[4]));
866         }
867 }
868
869 /*
870  * Function: pop_reset
871  *
872  * Purpose: Reset the server to its initial connect state
873  *
874  * Arguments:
875  *      server  The server.
876  *
877  * Return value: 0 for success, non-0 with error in pop_error
878  *      otherwise.
879  *
880  * Side effects: Closes the connection on error.
881  */
882 int
883 pop_reset (server)
884      popserver server;
885 {
886         if (pop_retrieve_flush (server)) {
887                 return (-1);
888         }
889
890         if (sendline (server, "RSET") || getok (server))
891                 return (-1);
892
893         return (0);
894 }
895
896 /*
897  * Function: pop_quit
898  *
899  * Purpose: Quit the connection to the server,
900  *
901  * Arguments:
902  *      server  The server to quit.
903  *
904  * Return value: 0 for success, non-zero otherwise with error in
905  *      pop_error.
906  *
907  * Side Effects: The popserver passed in is unusable after this
908  *      function is called, even if an error occurs.
909  */
910 int
911 pop_quit (server)
912      popserver server;
913 {
914         int ret = 0;
915
916         if (server->file >= 0) {
917                 if (pop_retrieve_flush (server)) {
918                         ret = -1;
919                 }
920
921                 if (sendline (server, "QUIT") || getok (server)) {
922                         ret = -1;
923                 }
924
925                 close (server->file);
926         }
927
928         if (server->buffer)
929                 free (server->buffer);
930         free ((char *) server);
931
932         return (ret);
933 }
934
935 /*
936  * Function: socket_connection
937  *
938  * Purpose: Opens the network connection with the mail host, without
939  *      doing any sort of I/O with it or anything.
940  *
941  * Arguments:
942  *      host    The host to which to connect.
943  *      flags   Option flags.
944  *
945  * Return value: A file descriptor indicating the connection, or -1
946  *      indicating failure, in which case an error has been copied
947  *      into pop_error.
948  */
949 static int
950 socket_connection (host, flags)
951      char *host;
952      int flags;
953 {
954         struct hostent *hostent;
955         struct servent *servent;
956         struct sockaddr_in addr;
957         char found_port = 0;
958         char *service;
959         int sock;
960 #if defined WITH_KERBEROS && defined HAVE_KERBEROS
961 # if defined HAVE_KERBEROS5
962         krb5_error_code rem;
963         krb5_context kcontext = 0;
964         krb5_auth_context auth_context = 0;
965         krb5_ccache ccdef;
966         krb5_principal client, server;
967         krb5_error *err_ret;
968         register char *cp;
969 # else  /* !HAVE_KERBEROS5 */
970         KTEXT ticket;
971         MSG_DAT msg_data;
972         CREDENTIALS cred;
973         Key_schedule schedule;
974         int rem;
975         char *realhost;
976 # endif /* HAVE_KERBEROS5 */
977 #endif /* WITH_KERBEROS && HAVE_KERBEROS */
978
979         int try_count = 0;
980
981         bzero ((char *) &addr, sizeof (addr));
982         addr.sin_family = AF_INET;
983
984         /** "kpop" service is  never used: look for 20060515 to see why **/
985 #if defined WITH_KERBEROS && defined HAVE_KERBEROS
986         service = (flags & POP_NO_KERBEROS) ? POP_SERVICE : KPOP_SERVICE;
987 #else
988         service = POP_SERVICE;
989 #endif
990
991 #ifdef HESIOD
992         if (! (flags & POP_NO_HESIOD)) {
993                 servent = hes_getservbyname (service, "tcp");
994                 if (servent) {
995                         addr.sin_port = servent->s_port;
996                         found_port = 1;
997                 }
998         }
999 #endif
1000         if (! found_port) {
1001                 servent = getservbyname (service, "tcp");
1002                 if (servent) {
1003                         addr.sin_port = servent->s_port;
1004                 } else {
1005                         /** "kpop" service is  never used:
1006                          * look for 20060515 to see why **/
1007 #if defined WITH_KERBEROS && defined HAVE_KERBEROS
1008                         addr.sin_port = htons ((flags & POP_NO_KERBEROS) ?
1009                                                POP_PORT : KPOP_PORT);
1010 #else
1011                         addr.sin_port = htons (POP_PORT);
1012 #endif
1013                 }
1014         }
1015
1016 #define POP_SOCKET_ERROR "Could not create socket for POP connection: "
1017
1018         sock = socket (PF_INET, SOCK_STREAM, 0);
1019         if (sock < 0) {
1020                 strcpy (pop_error, POP_SOCKET_ERROR);
1021                 strncat (pop_error, strerror (errno),
1022                          ERROR_MAX - sizeof (POP_SOCKET_ERROR));
1023                 return (-1);
1024
1025         }
1026
1027         do {
1028                 hostent = gethostbyname (host);
1029                 try_count++;
1030                 if ((! hostent) &&
1031                     ((h_errno != TRY_AGAIN) || (try_count == 5))) {
1032                         strcpy (pop_error, "Could not determine "
1033                                 "POP server's address");
1034                         return (-1);
1035                 }
1036         } while (! hostent);
1037
1038         while (*hostent->h_addr_list) {
1039                 bcopy (*hostent->h_addr_list, (char *) &addr.sin_addr,
1040                        hostent->h_length);
1041                 if (! connect (sock, (struct sockaddr *) &addr, sizeof (addr)))
1042                         break;
1043                 hostent->h_addr_list++;
1044         }
1045
1046 #define CONNECT_ERROR "Could not connect to POP server: "
1047
1048         if (! *hostent->h_addr_list) {
1049                 CLOSESOCKET (sock);
1050                 strcpy (pop_error, CONNECT_ERROR);
1051                 strncat (pop_error, strerror (errno),
1052                          ERROR_MAX - sizeof (CONNECT_ERROR));
1053                 return (-1);
1054         }
1055
1056 #if defined WITH_KERBEROS && defined HAVE_KERBEROS
1057 #define KRB_ERROR "Kerberos error connecting to POP server: "
1058         if (! (flags & POP_NO_KERBEROS)) {
1059 # if defined HAVE_KERBEROS5
1060                 if ((rem = krb5_init_context (&kcontext))) {
1061                 krb5error:
1062                         if (auth_context)
1063                                 krb5_auth_con_free (kcontext, auth_context);
1064                         if (kcontext)
1065                                 krb5_free_context (kcontext);
1066                         strcpy (pop_error, KRB_ERROR);
1067                         strncat (pop_error, error_message (rem),
1068                                  ERROR_MAX - sizeof(KRB_ERROR));
1069                         CLOSESOCKET (sock);
1070                         return (-1);
1071                 }
1072
1073                 if ((rem = krb5_auth_con_init (kcontext, &auth_context)))
1074                         goto krb5error;
1075
1076                 if (rem = krb5_cc_default (kcontext, &ccdef))
1077                         goto krb5error;
1078
1079                 if (rem = krb5_cc_get_principal (kcontext, ccdef, &client))
1080                         goto krb5error;
1081
1082                 for (cp = hostent->h_name; *cp; cp++) {
1083                         if (isupper (*cp)) {
1084                                 *cp = tolower (*cp);
1085                         }
1086                 }
1087
1088                 if (rem = krb5_sname_to_principal (kcontext, hostent->h_name,
1089                                                    POP_SERVICE, FALSE, &server))
1090                         goto krb5error;
1091
1092                 rem = krb5_sendauth (kcontext, &auth_context,
1093                                      (krb5_pointer) &sock, "KPOPV1.0",
1094                                      client, server,
1095                                      AP_OPTS_MUTUAL_REQUIRED,
1096                                      0, /* no checksum */
1097                                      0, /* no creds, use ccache instead */
1098                                      ccdef,
1099                                      &err_ret,
1100                                      0, /* don't need subsession key */
1101                                      0);        /* don't need reply */
1102                 krb5_free_principal (kcontext, server);
1103                 if (rem) {
1104                         if (err_ret && err_ret->text.length) {
1105                                 strcpy (pop_error, KRB_ERROR);
1106                                 strncat (pop_error, error_message (rem),
1107                                          ERROR_MAX - sizeof (KRB_ERROR));
1108                                 strncat (pop_error, " [server says '",
1109                                          ERROR_MAX - strlen (pop_error) - 1);
1110                                 strncat (pop_error, err_ret->text.data,
1111                                          min (ERROR_MAX -
1112                                               strlen (pop_error) - 1,
1113                                               err_ret->text.length));
1114                                 strncat (pop_error, "']",
1115                                          ERROR_MAX - strlen (pop_error) - 1);
1116                         } else {
1117                                 strcpy (pop_error, KRB_ERROR);
1118                                 strncat (pop_error, error_message (rem),
1119                                          ERROR_MAX - sizeof (KRB_ERROR));
1120                         }
1121                         if (err_ret)
1122                                 krb5_free_error (kcontext, err_ret);
1123                         krb5_auth_con_free (kcontext, auth_context);
1124                         krb5_free_context (kcontext);
1125
1126                         CLOSESOCKET (sock);
1127                         return (-1);
1128                 }
1129 # else  /* ! HAVE_KERBEROS5 */
1130                 ticket = (KTEXT) malloc (sizeof (KTEXT_ST));
1131                 realhost = strdup (hostent->h_name);
1132                 rem = krb_sendauth (0L, sock, ticket, "pop", realhost,
1133                                     (char *) krb_realmofhost (realhost),
1134                                     (unsigned long) 0, &msg_data, &cred, schedule,
1135                                     (struct sockaddr_in *) 0,
1136                                     (struct sockaddr_in *) 0,
1137                                     "KPOPV0.1");
1138                 free ((char *) ticket);
1139                 free (realhost);
1140                 if (rem != KSUCCESS) {
1141                         strcpy (pop_error, KRB_ERROR);
1142                         strncat (pop_error, krb_err_txt[rem],
1143                                  ERROR_MAX - sizeof (KRB_ERROR));
1144                         CLOSESOCKET (sock);
1145                         return (-1);
1146                 }
1147 # endif /* HAVE_KERBEROS5 */
1148         }
1149 #endif /* WITH_KERBEROS && HAVE_KERBEROS */
1150
1151         return (sock);
1152 } /* socket_connection */
1153
1154 /*
1155  * Function: pop_getline
1156  *
1157  * Purpose: Get a line of text from the connection and return a
1158  *      pointer to it.  The carriage return and linefeed at the end of
1159  *      the line are stripped, but periods at the beginnings of lines
1160  *      are NOT dealt with in any special way.
1161  *
1162  * Arguments:
1163  *      server  The server from which to get the line of text.
1164  *
1165  * Returns: The number of characters in the line, which is returned in
1166  *      LINE, not including the final null.  A return value of 0
1167  *      indicates a blank line.  A negative return value indicates an
1168  *      error (in which case the contents of LINE are undefined.  In
1169  *      case of error, an error message is copied into pop_error.
1170  *
1171  * Notes: The line returned is overwritten with each call to pop_getline.
1172  *
1173  * Side effects: Closes the connection on error.
1174  *
1175  * THE RETURNED LINE MAY CONTAIN EMBEDDED NULLS!
1176  */
1177 static int
1178 pop_getline (server, line)
1179      popserver server;
1180      char **line;
1181 {
1182 #define GETLINE_ERROR "Error reading from server: "
1183
1184         int ret;
1185         int search_offset = 0;
1186
1187         if (server->data) {
1188                 char *cp = find_crlf (server->buffer + server->buffer_index,
1189                                       server->data);
1190                 if (cp) {
1191                         int found;
1192                         int data_used;
1193
1194                         found = server->buffer_index;
1195                         data_used = (cp + 2) - server->buffer - found;
1196
1197                         /* terminate the string to be returned */
1198                         *cp = '\0';
1199                         server->data -= data_used;
1200                         server->buffer_index += data_used;
1201
1202                         if (pop_debug)
1203                                 /* Embedded nulls will truncate this output
1204                                    prematurely, but that's OK because it's just
1205                                    for debugging anyway. */
1206                                 fprintf (stderr, "<<< %s\n",
1207                                          server->buffer + found);
1208                         *line = server->buffer + found;
1209                         return (data_used - 2);
1210                 } else {
1211                         bcopy (server->buffer + server->buffer_index,
1212                                server->buffer, server->data);
1213                         /* Record the fact that we've searched the data already
1214                            in the buffer for a CRLF, so that when we search
1215                            below, we don't have to search the same data twice.
1216                            There's a "- 1" here to account for the fact that the
1217                            last character of the data we have may be the CR of a
1218                            CRLF pair, of which we haven't read the second half
1219                            yet, so we may have to search it again when we read
1220                            more data. */
1221                         search_offset = server->data - 1;
1222                         server->buffer_index = 0;
1223                 }
1224         } else {
1225                 server->buffer_index = 0;
1226         }
1227
1228         while (1) {
1229                 /* There's a "- 1" here to leave room for the null that we put
1230                    at the end of the read data below.  We put the null there so
1231                    that find_crlf knows where to stop when we call it. */
1232                 if (server->data == server->buffer_size - 1) {
1233                         server->buffer_size += GETLINE_INCR;
1234                         server->buffer = (char *)realloc (
1235                                 server->buffer, server->buffer_size);
1236                         if (! server->buffer) {
1237                                 strcpy (pop_error,
1238                                         "Out of memory in pop_getline");
1239                                 pop_trash (server);
1240                                 return (-1);
1241                         }
1242                 }
1243                 ret = RECV (server->file, server->buffer + server->data,
1244                             server->buffer_size - server->data - 1, 0);
1245                 if (ret < 0) {
1246                         strcpy (pop_error, GETLINE_ERROR);
1247                         strncat (pop_error, strerror (errno),
1248                                  ERROR_MAX - sizeof (GETLINE_ERROR));
1249                         pop_trash (server);
1250                         return (-1);
1251                 } else if (ret == 0) {
1252                         strcpy (pop_error, "Unexpected EOF from "
1253                                 "server in pop_getline");
1254                         pop_trash (server);
1255                         return (-1);
1256                 } else {
1257                         char *cp;
1258                         server->data += ret;
1259                         server->buffer[server->data] = '\0';
1260
1261                         cp = find_crlf (server->buffer + search_offset,
1262                                         server->data - search_offset);
1263                         if (cp) {
1264                                 int data_used = (cp + 2) - server->buffer;
1265                                 *cp = '\0';
1266                                 server->data -= data_used;
1267                                 server->buffer_index = data_used;
1268
1269                                 if (pop_debug)
1270                                         fprintf (stderr,
1271                                                  "<<< %s\n", server->buffer);
1272                                 *line = server->buffer;
1273                                 return (data_used - 2);
1274                         }
1275                         /* As above, the "- 1" here is to account for the fact that
1276                            we may have read a CR without its accompanying LF. */
1277                         search_offset += ret - 1;
1278                 }
1279         }
1280
1281         /* NOTREACHED */
1282 }
1283
1284 /*
1285  * Function: sendline
1286  *
1287  * Purpose: Sends a line of text to the POP server.  The line of text
1288  *      passed into this function should NOT have the carriage return
1289  *      and linefeed on the end of it.  Periods at beginnings of lines
1290  *      will NOT be treated specially by this function.
1291  *
1292  * Arguments:
1293  *      server  The server to which to send the text.
1294  *      line    The line of text to send.
1295  *
1296  * Return value: Upon successful completion, a value of 0 will be
1297  *      returned.  Otherwise, a non-zero value will be returned, and
1298  *      an error will be copied into pop_error.
1299  *
1300  * Side effects: Closes the connection on error.
1301  */
1302 static int
1303 sendline (server, line)
1304      popserver server;
1305      char *line;
1306 {
1307 #define SENDLINE_ERROR "Error writing to POP server: "
1308         int ret;
1309         char *buf;
1310
1311         /* Combine the string and the CR-LF into one buffer.  Otherwise, two
1312            reasonable network stack optimizations, Nagle's algorithm and
1313            delayed acks, combine to delay us a fraction of a second on every
1314            message we send.  (Movemail writes line without \r\n, client
1315            kernel sends packet, server kernel delays the ack to see if it
1316            can combine it with data, movemail writes \r\n, client kernel
1317            waits because it has unacked data already in its outgoing queue,
1318            client kernel eventually times out and sends.)
1319
1320            This can be something like 0.2s per command, which can add up
1321            over a few dozen messages, and is a big chunk of the time we
1322            spend fetching mail from a server close by.  */
1323         buf = alloca (strlen (line) + 3);
1324         strcpy (buf, line);
1325         strcat (buf, "\r\n");
1326         ret = fullwrite (server->file, buf, strlen (buf));
1327
1328         if (ret < 0) {
1329                 pop_trash (server);
1330                 strcpy (pop_error, SENDLINE_ERROR);
1331                 strncat (pop_error, strerror (errno),
1332                          ERROR_MAX - sizeof (SENDLINE_ERROR));
1333                 return (ret);
1334         }
1335
1336         if (pop_debug)
1337                 fprintf (stderr, ">>> %s\n", line);
1338
1339         return (0);
1340 }
1341
1342 /*
1343  * Procedure: fullwrite
1344  *
1345  * Purpose: Just like write, but keeps trying until the entire string
1346  *      has been written.
1347  *
1348  * Return value: Same as write.  Pop_error is not set.
1349  */
1350 static int
1351 fullwrite (fd, buf, nbytes)
1352      int fd;
1353      char *buf;
1354      int nbytes;
1355 {
1356         char *cp;
1357         int ret = 0;
1358
1359         cp = buf;
1360         while (nbytes && ((ret = SEND (fd, cp, nbytes, 0)) > 0)) {
1361                 cp += ret;
1362                 nbytes -= ret;
1363         }
1364
1365         return (ret);
1366 }
1367
1368 /*
1369  * Procedure getok
1370  *
1371  * Purpose: Reads a line from the server.  If the return indicator is
1372  *      positive, return with a zero exit status.  If not, return with
1373  *      a negative exit status.
1374  *
1375  * Arguments:
1376  *      server  The server to read from.
1377  *
1378  * Returns: 0 for success, else for failure and puts error in pop_error.
1379  *
1380  * Side effects: On failure, may make the connection unusable.
1381  */
1382 static int
1383 getok (server)
1384      popserver server;
1385 {
1386         char *fromline;
1387
1388         if (pop_getline (server, &fromline) < 0) {
1389                 return (-1);
1390         }
1391
1392         if (! strncmp (fromline, "+OK", 3))
1393                 return (0);
1394         else if (! strncmp (fromline, "-ERR", 4)) {
1395                 xstrncpy (pop_error, fromline, ERROR_MAX);
1396                 pop_error[ERROR_MAX-1] = '\0';
1397                 return (-1);
1398         } else {
1399                 strcpy (pop_error,
1400                         "Unexpected response from server; "
1401                         "expecting +OK or -ERR");
1402                 pop_trash (server);
1403                 return (-1);
1404         }
1405 }
1406
1407 #if 0
1408 /*
1409  * Function: gettermination
1410  *
1411  * Purpose: Gets the next line and verifies that it is a termination
1412  *      line (nothing but a dot).
1413  *
1414  * Return value: 0 on success, non-zero with pop_error set on error.
1415  *
1416  * Side effects: Closes the connection on error.
1417  */
1418 static int
1419 gettermination (server)
1420      popserver server;
1421 {
1422         char *fromserver;
1423
1424         if (pop_getline (server, &fromserver) < 0)
1425                 return (-1);
1426
1427         if (strcmp (fromserver, ".")) {
1428                 strcpy (pop_error,
1429                         "Unexpected response from server in gettermination");
1430                 pop_trash (server);
1431                 return (-1);
1432         }
1433
1434         return (0);
1435 }
1436 #endif
1437
1438 /*
1439  * Function pop_close
1440  *
1441  * Purpose: Close a pop connection, sending a "RSET" command to try to
1442  *      preserve any changes that were made and a "QUIT" command to
1443  *      try to get the server to quit, but ignoring any responses that
1444  *      are received.
1445  *
1446  * Side effects: The server is unusable after this function returns.
1447  *      Changes made to the maildrop since the session was started (or
1448  *      since the last pop_reset) may be lost.
1449  */
1450 void
1451 pop_close (server)
1452      popserver server;
1453 {
1454         pop_trash (server);
1455         free ((char *) server);
1456
1457         return;
1458 }
1459
1460 /*
1461  * Function: pop_trash
1462  *
1463  * Purpose: Like pop_close or pop_quit, but doesn't deallocate the
1464  *      memory associated with the server.  It is legal to call
1465  *      pop_close or pop_quit after this function has been called.
1466  */
1467 static void
1468 pop_trash (server)
1469      popserver server;
1470 {
1471         if (server->file >= 0) {
1472                 /* avoid recursion; sendline can call pop_trash */
1473                 if (server->trash_started)
1474                         return;
1475                 server->trash_started = 1;
1476
1477                 sendline (server, "RSET");
1478                 sendline (server, "QUIT");
1479
1480                 CLOSESOCKET (server->file);
1481                 server->file = -1;
1482                 if (server->buffer) {
1483                         free (server->buffer);
1484                         server->buffer = 0;
1485                 }
1486         }
1487 }
1488
1489 /* Return a pointer to the first CRLF in IN_STRING, which can contain
1490    embedded nulls and has LEN characters in it not including the final
1491    null, or 0 if it does not contain one.  */
1492
1493 static char *
1494 find_crlf (in_string, len)
1495      char *in_string;
1496      int len;
1497 {
1498         while (len--) {
1499                 if (*in_string == '\r') {
1500                         if (*++in_string == '\n')
1501                                 return (in_string - 1);
1502                 } else
1503                         in_string++;
1504         }
1505         return (0);
1506 }
1507
1508 #endif /* MAIL_USE_POP */