/* Copyright 1993-95 by Carl Harris, Jr. * All rights reserved * * Distribute freely, except: don't remove my name from the source or * documentation (don't take credit for my work), mark your changes (don't * get me blamed for your possible bugs), don't alter or remove this * notice. May be sold if buildable source is provided to buyer. No * warrantee of any kind, express or implied, is included with this * software; use at your own risk, responsibility for damages (if any) to * anyone resulting from the use of this software rests entirely with the * user. * * Send bug reports, bug fixes, enhancements, requests, flames, etc., and * I'll try to keep a version up to date. I can be reached as follows: * Carl Harris */ /*********************************************************************** module: pop2.c project: popclient programmer: Carl Harris, ceharris@mal.com description: POP2 client code. $Log: pop2.c,v $ Revision 1.2 1996/06/26 19:08:57 esr This is what I sent Harris. Revision 1.1 1996/06/24 19:00:51 esr Initial revision Revision 1.6 1995/08/14 18:36:40 ceharris Patches to support POP3's LAST command. Final revisions for beta3 release. Revision 1.5 1995/08/10 00:32:36 ceharris Preparation for 3.0b3 beta release: - added code for --kill/--keep, --limit, --protocol, --flush options; --pop2 and --pop3 options now obsoleted by --protocol. - added support for APOP authentication, including --with-APOP argument for configure. - provisional and broken support for RPOP - added buffering to SockGets and SockRead functions. - fixed problem of command-line options not being correctly carried into the merged options record. Revision 1.4 1995/08/09 01:32:53 ceharris Version 3.0 beta 2 release. Added - .poprc functionality - GNU long options - multiple servers on the command line. Fixed - Passwords showing up in ps output. Revision 1.3 1995/08/08 01:01:22 ceharris Added GNU-style long options processing. Fixed password in 'ps' output problem. Fixed various RCS tag blunders. Integrated .poprc parser, lexer, etc into Makefile processing. ***********************************************************************/ #include #include #if defined(STDC_HEADERS) #include #endif #if defined(HAVE_UNISTD_H) #include #endif #include #include #include "socket.h" #include "popclient.h" /* TCP port number for POP2 as defined by RFC 937 */ #define POP2_PORT 109 #if HAVE_PROTOTYPES /* prototypes for internal functions */ int POP2_sendcmd (char *cmd, int socket); int POP2_sendHELO (char *userid, char *password, int socket); int POP2_sendFOLD (char *folder, int socket); int POP2_quit (int socket); int POP2_stateGREET (int socket); int POP2_stateNMBR (int socket); int POP2_stateSIZE (int socket); int POP2_stateXFER (int msgsize, int socket, int mboxfd, int topipe); #endif /********************************************************************* function: doPOP2 description: retrieve messages from the specified mail server using Post Office Protocol 2. arguments: servername name of the server to which we'll connect. options fully-specified options (i.e. parsed, defaults invoked, etc). return value: exit code from the set of PS_.* constants defined in popclient.h calls: POP2_stateGREET, POP2_stateNMBR, POP2_stateSIZE, POP2_stateXFER, POP2_sendcmd, POP2_sendHELO, POP2_sendFOLD, POP2_quit, Socket, openuserfolder, closeuserfolder, openmailpipe, closemailpipe. globals: reads outlevel. *********************************************************************/ int doPOP2 (servername,options) char *servername; struct optrec *options; { int mboxfd; int socket; int number,msgsize,actsize; int status = PS_UNDEFINED; /* check for unsupported options */ if (options->limit) { fprintf(stderr,"Option --limit is not supported in POP2\n"); return(PS_SYNTAX); } else if (options->flush) { fprintf(stderr,"Option --flush is not supported in POP2\n"); return(PS_SYNTAX); } else if (options->fetchall) { fprintf(stderr,"Option --all is not supported in POP2\n"); return(PS_SYNTAX); } else ; /* open the socket to the POP server */ if ((socket = Socket(servername,POP2_PORT)) < 0) { perror("doPOP2: socket"); return(PS_SOCKET); } /* open/lock the folder if it is a user folder or stdout */ if (options->output == TO_FOLDER) if ((mboxfd = openuserfolder(options)) < 0) return(PS_IOERR); /* wait for the POP2 greeting */ if (POP2_stateGREET(socket) != 0) { POP2_quit(socket); status = PS_PROTOCOL; goto closeUp; } /* log the user onto the server */ POP2_sendHELO(options->username,options->password,socket); if ((number = POP2_stateNMBR(socket)) < 0) { POP2_quit(socket); status = PS_AUTHFAIL; goto closeUp; } /* set the remote folder if selected */ if (*options->remotefolder != 0) { POP2_sendFOLD(options->remotefolder,socket); if ((number = POP2_stateNMBR(socket)) < 0) { POP2_quit(socket); status = PS_PROTOCOL; goto closeUp; } } /* tell 'em how many messages are waiting */ if (outlevel > O_SILENT && outlevel < O_VERBOSE) fprintf(stderr,"%d messages in folder %s\n",number,options->remotefolder); else ; /* fall into a retrieve/acknowledge loop */ if (number > 0) { POP2_sendcmd("READ",socket); msgsize = POP2_stateSIZE(socket); while (msgsize > 0) { /* open the pipe */ if (options->output == TO_MDA) if ((mboxfd = openmailpipe(options)) < 0) { POP2_quit(socket); return(PS_IOERR); } POP2_sendcmd("RETR",socket); actsize = POP2_stateXFER(msgsize,socket,mboxfd, options->output == TO_MDA); if (actsize == msgsize) if (options->keep) POP2_sendcmd("ACKS",socket); else POP2_sendcmd("ACKD",socket); else if (actsize >= 0) POP2_sendcmd("NACK",socket); else { POP2_quit(socket); status = PS_SOCKET; goto closeUp; } /* close the pipe */ if (options->output == TO_MDA) if (closemailpipe(mboxfd) < 0) { POP2_quit(socket); status = PS_IOERR; goto closeUp; } msgsize = POP2_stateSIZE(socket); } POP2_quit(socket); status = msgsize == 0 ? PS_SUCCESS : PS_PROTOCOL; } else { POP2_quit(socket); status = PS_NOMAIL; } closeUp: if (options->output == TO_FOLDER) closeuserfolder(mboxfd); return(status); } /********************************************************************* function: POP2_sendcmd description: send a command string (with no arguments) a server. arguments: cmd command string to send. socket socket to which the server is connected. return value: none. calls: SockPuts. globals: reads outlevel. *********************************************************************/ int POP2_sendcmd (cmd,socket) char *cmd; int socket; { SockPuts(socket,cmd); if (outlevel == O_VERBOSE) fprintf(stderr,"> %s\n",cmd); else ; } /********************************************************************* function: POP2_sendHELO description: send the HELO command to the server. arguments: userid user's mailserver id. password user's mailserver password. socket socket to which the server is connected. return value: none. calls: SockPrintf. globals: read outlevel. *********************************************************************/ int POP2_sendHELO (userid,password,socket) char *userid, *password; int socket; { SockPrintf(socket,"HELO %s %s\r\n",userid,password); if (outlevel == O_VERBOSE) fprintf(stderr,"> HELO %s password\n",userid); else ; } /********************************************************************* function: POP2_sendFOLD description: send the FOLD command to the server. arguments: folder name of the folder to open on the server. socket socket to which the server is connected. return value: none. calls: SockPrintf. globals: reads outlevel. *********************************************************************/ int POP2_sendFOLD (folder,socket) char *folder; int socket; { SockPrintf(socket,"FOLD %s\r\n",folder); if (outlevel == O_VERBOSE) fprintf(stderr,"> FOLD %s\n",folder); else ; } /********************************************************************* function: POP2_quit description: send the QUIT command to the server and close the socket. arguments: socket socket to which the server is connected. return value: none. calls: SockPuts. globals: reads outlevel. *********************************************************************/ int POP2_quit (socket) int socket; { SockPuts(socket,"QUIT"); close(socket); if (outlevel == O_VERBOSE) fprintf(stderr,"> QUIT\n"); else ; } /********************************************************************* function: POP2_stateGREET description: process the GREET state as described in RFC 937. arguments: socket ...to which server is connected. return value: zero if server's handling of the GREET state was correct, else non-zero (may indicate difficulty at the socket). calls: SockGets. globals: reads outlevel. *********************************************************************/ int POP2_stateGREET (socket) int socket; { char buf [POPBUFSIZE]; /* read the greeting from the server */ if (SockGets(socket, buf, sizeof(buf)) == 0) { /* echo the server's greeting to the user */ if (outlevel > O_SILENT) fprintf(stderr,"%s\n",buf); else ; /* is the greeting in the correct format? */ if (*buf == '+') return(0); else return(-1); } else { /* an error at the socket */ if (outlevel > O_SILENT) perror("error reading socket\n"); else ; return(-1); } } /********************************************************************* function: POP2_stateNMBR description: process the NMBR state as described in RFC 937. arguments: socket ...to which the server is connected. return value: zero if the expected NMBR state action occured, else non-zero. Following HELO, a non-zero return value usually here means the user authorization at the server failed. calls: SockGets. globals: reads outlevel. *********************************************************************/ int POP2_stateNMBR (socket) int socket; { int number; char buf [POPBUFSIZE]; /* read the NMBR (#ccc) message from the server */ if (SockGets(socket, buf, sizeof(buf)) == 0) { /* is the message in the proper format? */ if (*buf == '#') { number = atoi(buf + 1); if (outlevel == O_VERBOSE) fprintf(stderr,"%s\n",buf); else ; } else { number = -1; if (outlevel > O_SILENT) fprintf(stderr,"%s\n",buf); else ; } } else { /* socket problem */ number = -1; if (outlevel == O_VERBOSE) perror("socket read error\n"); else ; } return(number); } /********************************************************************* function: POP2_stateSIZE description: process the SIZE state as described in RFC 937. arguments: socket ...to which the server is connected. return value: zero if the expected SIZE state action occured, else non-zero (usually indicates a protocol violation). calls: SockGets. globals: reads outlevel. *********************************************************************/ int POP2_stateSIZE (socket) int socket; { int msgsize; char buf [POPBUFSIZE]; /* read the SIZE message (=ccc) from the server */ if (SockGets(socket, buf, sizeof(buf)) == 0) /* is the message in the correct format? */ if (*buf == '=') { msgsize = atoi(buf + 1); if (outlevel == O_VERBOSE) fprintf(stderr,"%s\n",buf); else ; } else { msgsize = -1; if (outlevel > O_SILENT) fprintf(stderr,"%s\n",buf); else ; } else { /* socket problem */ msgsize = -1; if (outlevel == O_VERBOSE) perror("socket read error\n"); else ; } return(msgsize); } /********************************************************************* function: POP2_stateXFER description: process the XFER state as described in RFC 937. arguments: msgsize content length of the message as reported in the SIZE state. socket ... to which the server is connected. mboxfd open file descriptor to which the retrieved message will be written. topipe true if we're writing to a the /bin/mail pipe. return value: >= 0 actual length of the message received. < 0 socket I/O problem. calls: SockRead. globals: reads outlevel. *********************************************************************/ int POP2_stateXFER (msgsize,socket,mboxfd,topipe) int msgsize; int socket; int mboxfd; int topipe; { int i,buflen,actsize; char buf [MSGBUFSIZE]; char frombuf [MSGBUFSIZE]; char savec; int msgTop; int needFrom; time_t now; /* This keeps the retrieved message count for display purposes */ static int msgnum = 0; /* set up for status message if outlevel allows it */ if (outlevel > O_SILENT && outlevel < O_VERBOSE) { fprintf(stderr,"reading message %d",++msgnum); /* won't do the '...' if retrieved messages are being sent to stdout */ if (mboxfd == 1) /* we're writing to stdout */ fputs(".\n",stderr); else ; } else ; /* read the specified message content length from the server */ actsize = 0; msgTop = !0; while (msgsize > 0) { buflen = msgsize <= MSGBUFSIZE ? msgsize : MSGBUFSIZE; /* read a bufferful */ if (SockRead(socket, buf, buflen) == 0) { /* Check for Unix 'From' header, and add bogus one if it's not present -- only if not using an MDA. XXX -- should probably parse real From: header and use its address field instead of bogus 'POPmail' string. */ if (!topipe && msgTop) { msgTop = 0; if (strlen(buf) >= strlen("From ")) { savec = *(buf + 5); *(buf + 5) = 0; needFrom = strcmp(buf,"From ") != 0; *(buf + 5) = savec; } else needFrom = 1; if (needFrom) { now = time(NULL); sprintf(frombuf,"From POPmail %s",ctime(&now)); if (write(mboxfd,frombuf,strlen(frombuf)) < 0) { perror("POP2_stateXFER: write"); return(-1); } } } /* write to folder, stripping CR chars in the process */ for (i = 0; i < buflen; i++) if (*(buf + i) != '\r') if (write(mboxfd,buf + i,1) < 0) { perror("POP2_stateXFER: write"); return(-1); } else ; /* it was written */ else ; /* ignore CR character */ } else return(-1); /* socket problem */ /* write another . for every bufferful received */ if (outlevel > O_SILENT && outlevel < O_VERBOSE && mboxfd != 1) fputc('.',stderr); else ; msgsize -= buflen; actsize += buflen; } if (!topipe) { /* The server may not write the extra newline required by the Unix mail folder format, so we write one here just in case */ if (write(mboxfd,"\n",1) < 1) { perror("POP2_stateXFER: write"); return(-1); } } else { /* the mailer might require some sort of termination string, send it if it is defined */ #ifdef BINMAIL_TERM if (write(mboxfd,BINMAIL_TERM,strlen(BINMAIL_TERM)) < 0) { perror("POP2_stateXFER: write"); return(-1); } #endif } /* finish up display output */ if (outlevel == O_VERBOSE) fprintf(stderr,"(%d characters of message content)\n",actsize); else if (outlevel > O_SILENT && mboxfd != 0) fputc('\n',stderr); else ; return(actsize); }