1 /* mdpop3d.c
2    POP3 daemon for MailDirs.
3 
4    Copyright (C) 2000, 2001 Michael Tokarev.
5 
6    This program is free software; you can redistribute it and/or
7    modify it under the terms of the GNU General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    License, or (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    General Public License for more details.
15 
16    The author of the program may be contacted at mjt@corpit.ru
17 */
18 
19 /* to compile with PAM support, #define USE_PAM. */
20 /* to compile with APOP command support, #define USE_APOP (PAM required) */
21 
22 /* todo:
23  Server-side (forced or per-user) message expiration (maybe)
24  AUTH command (rfc1734, rfc2195) support?
25  RESP-CODES (rfc2449) support?
26  LOGIN-DELAY (rfc2449) support?
27  Resolving of remote ip address, with verify (maybe)
28  Chroot to maildir for safety (not clean task, requires switching uids
29   from/to root)
30  Reporting of error conditions (like OOM, unable to open file etc) (maybe)
31  Store message size *in* maildir filename, like e.g. courier does?
32  Submailboxes support ala courier? (with username+folder)
33  Provide an example of pam_apop module?
34  STLS command? (requires me to understand how e.g. sasl works and if this can
35   be done via PAM w/o the whole sasl thing -- using openssl for encryption and
36   PAM for auth)
37  More efficient maildir.  This is to say -- do not use maildir format, but
38   other similar format with one message per file, all in one dir, with possible
39   hashing.  This needs to be syncronized with other systems that use this mail
40   storage...
41  Look more closely to order of PAM calls.  According to last messages in
42   Linux-PAM mailinglist, something should be wrong with pam_setcred() calling
43   order.  The evil thing with PAM is that it does nasty things with syslog,
44   and that's a real PITA to deal with...
45 */
46 
47 #include <unistd.h>
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <time.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
53 #include <arpa/inet.h>
54 #include <signal.h>
55 #include <string.h>
56 #include <stdio.h>
57 #include <stdarg.h>
58 #include <pwd.h>
59 #include <grp.h>
60 #include <syslog.h>
61 #include <errno.h>
62 #include <stdlib.h>
63 #include <dirent.h>
64 #ifdef USE_PAM
65 #include <security/pam_appl.h>
66 #else
67 #ifdef USE_APOP
68 # error APOP requires PAM
69 #endif
70 #include <pwd.h>
71 #include <unistd.h>
72 #endif
73 
74 static const char *rhost; /* = NULL; remote host/addr for logging */
75 static const char *user; /* = NULL; username */
76 
77 static int timeout = 60; /* general i/o timeout */
78 static const char *maildir; /* = NULL; name of maildir */
79 static int debug; /* = 0; debug flag */
80 static int commit_on_err; /* = 0; if true, issue QUIT on comm error */
81 static int nomove; /* = 0; do not move messages to cur/ */
82 
83 static const char *const version = "mdpop3 " VERSION;
84 static const char *const maildirenv = "MAILDIR"; /* name of Maildir env. var */
85 
86 static char line[1024]; /* this is main buffer; used for output also */
87 static int llen; /* = 0; for buffered writes */
88 
89 #define OK "+OK "
90 #define ERR "-ERR "
91 
92 #ifdef __GNUC__
93 static void putline(const char *fmt, ...) __attribute__((format(printf,1,2)));
94 static void putmline(const char *fmt, ...) __attribute__((format(printf,1,2)));
95 static void die(const char *why) __attribute__((noreturn));
96 #else
97 static void die(const char *why);
98 #endif
99 
100 /* write to client with timeout */
twrite(const char * b,int l)101 static void twrite(const char *b, int l) {
102   alarm(timeout);
103   if (write(1, b, l) != l)
104     die("client write error");
105   alarm(0);
106 }
107 
108 /* put a line to client.  Note that this routine uses line[] buffer */
putline(const char * fmt,...)109 static void putline(const char *fmt, ...) {
110   int l;
111   va_list ap;
112   va_start(ap, fmt);
113   l = vsnprintf(line, sizeof(line) - 2, fmt, ap);
114   va_end(ap);
115   if (debug) syslog(LOG_DEBUG, "out: %s", line);
116   line[l++] = '\r'; line[l++] = '\n';
117   twrite(line, l);
118 }
119 
120 /* buffered client writes.
121   flushmline() flushes the buffer
122   putmline() similar to putline() but adds to buffer
123   line[] used for buffering!
124  */
125 
flushmline()126 static void flushmline() {
127   if (llen) {
128     twrite(line, llen);
129     llen = 0;
130   }
131 }
132 
putmline(const char * fmt,...)133 static void putmline(const char *fmt, ...) {
134   int l;
135   for(;;) {
136     if (llen < sizeof(line) - 3) {
137       va_list ap;
138       va_start(ap, fmt);
139       l = vsnprintf(line + llen, sizeof(line) - 2 - llen, fmt, ap);
140       va_end(ap);
141       if (l > 0 && l + llen < sizeof(line) - 2)
142         break;
143     }
144     /* not enouth space */
145     flushmline();
146   }
147   if (debug) syslog(LOG_DEBUG, "out: %s", line + llen);
148   llen += l;
149   line[llen++] = '\r'; line[llen++] = '\n';
150 }
151 
ok()152 static void ok() { putline("+OK"); }
needarg()153 static void needarg() { putline(ERR "argument expected"); }
extraarg()154 static void extraarg() { putline(ERR "unexpected argument"); }
needauth()155 static void needauth() { putline(ERR "user/pass first"); }
156 
157 static struct msg {
158   int flag;
159   off_t size;
160   time_t time;
161   char fn[1]; /* varstr */
162 } **msgp; /* = NULL; array of message pointers */
163 static int nmsg; /* = 0; total number of messages */
164 static int amsg; /* = 0; allocated size of *msgp (count of msgs) */
165 static int cmsg; /* = 0; current number of messages (excluding deleted) */
166 static off_t msgsz; /* = 0; current size of undeleted messages */
167 static int o_nmsg; /* = 0; original number of messages */
168 static off_t o_msgsz; /* = 0; original size of all messages */
169 
170 #define F_DELETED 0x01
171 #define F_NEW     0x02
172 #define F_SEEN    0x04
173 
nomem(int size)174 static void nomem(int size) { /* report OOM condition */
175   syslog(LOG_ERR, "unable to allocate %d bytes of memory", size);
176 }
177 
addmsg(const char * fn,off_t size,time_t time,int flag)178 int addmsg(const char *fn, off_t size, time_t time, int flag) {
179 
180 /* addmsg is only one place where we'll allocate memory.
181  Since pop3d will never free message structures memory, we use
182  a trivial wrapper around malloc that allocate large chunks of
183  memory at once and fill it up with message structs, allocating
184  new block when current one will be filled.
185  */
186   static char *m_bptr; /* = NULL; pointer to last allocated free space */
187   static int m_bsize;  /* = 0; size available on last block */
188 #ifndef MCSIZE
189 # define MCSIZE 10240 /* size of one block */
190 #endif
191 
192   int a, b, i;
193   struct msg *m;
194 
195   /* allocate memory first */
196   i = sizeof(struct msg) + strlen(fn); /* size of block to alloc */
197   if (m_bsize < i) { /* last block does not have needed space */
198     char *p = (char*)malloc(MCSIZE);
199     if (!p) {
200       nomem(MCSIZE);
201       return -1;
202     }
203     m_bptr = p;
204     m_bsize = MCSIZE;
205   }
206   m = (struct msg *)m_bptr;
207 
208   /* round i to sizeof(int) */
209   if ((a = i % sizeof(int)) != 0)
210     i += sizeof(int) - a;
211   m_bptr += i;
212   m_bsize -= i; /* it's Ok but unlikely to have m_bsize < 0 here */
213 
214   /* init message struct */
215   m->flag = flag; m->size = size; m->time = time;
216   strcpy(m->fn, fn);
217 
218   if (nmsg == amsg) { /* need to (re)allocate pointers */
219     struct msg **nm;
220     if (!msgp)
221       /* allocate 128 message entries initially */
222       nm = (struct msg **)malloc((i = 128) * sizeof(struct msg *));
223     else
224       /* double allocated message count each time */
225       nm = (struct msg **)realloc(msgp, (i = nmsg * 2) * sizeof(struct msg *));
226     if (!nm) {
227       nomem(i * sizeof(struct msg *));
228       /* "return" last allocated message buffer back */
229       m_bsize += (m_bptr - (char*)m);
230       m_bptr = (char*)m;
231       return -1;
232     }
233     msgp = nm;
234     amsg = i;
235   }
236 
237   /* find a place for new message, sorting by time */
238   a = 0; b = nmsg - 1;
239   while(a <= b) {
240     i = (a + b) >> 1;
241     if (msgp[i]->time < time)
242       a = i + 1;
243     else if (msgp[i]->time > time)
244       b = i - 1;
245     else {
246       a = i;
247       break;
248     }
249   }
250 
251   /* insert new message */
252   if (a < nmsg)
253     memmove(msgp + a + 1, msgp + a, (nmsg - a) * sizeof(struct msg *));
254   msgp[a] = m;
255   ++nmsg; ++cmsg; msgsz += size;
256 
257   return a;
258 }
259 
info()260 static void info() {
261   putline(OK "maildir: %d message(s) %ld octet(s)", cmsg, (long int)msgsz);
262 }
263 
msgfn(int n)264 static char *msgfn(int n) {
265   if (strlen(msgp[n]->fn) >= sizeof(line) - 4)
266     return NULL;
267   strcpy(line, msgp[n]->flag & F_NEW ? "new/" : "cur/");
268   strcpy(line + 4, msgp[n]->fn);
269   return line;
270 }
271 
initmaildir()272 static void initmaildir() {
273   DIR *dir;
274   struct dirent *de;
275   struct stat st;
276   int f;
277 
278   if (!maildir && (maildir = getenv(maildirenv)) == NULL)
279     maildir = "Maildir";
280 
281   if (chdir(maildir) != 0) {
282     /* assume no mail if no maildir */
283     info();
284     return;
285   }
286 
287   /* cleanup tmp dir */
288   if ((dir = opendir("tmp")) != NULL) {
289     time_t now = time(NULL);
290     strcpy(line, "tmp/");
291     while((de = readdir(dir)) != NULL) {
292       if (de->d_name[0] != '.' &&
293 	  strlen(de->d_name) < sizeof(line) - 4 &&
294 	  stat((strcpy(line+4, de->d_name), line), &st) == 0 &&
295 	  now - 36*60*60 > st.st_atime)
296 	unlink(line);
297     }
298     closedir(dir);
299   }
300 
301   /* scan new and cur */
302   /* we're totally quiet about errors here, just skipping them */
303   f = F_NEW;
304   strcpy(line, "new/");
305   for(;;) {
306     if ((dir = opendir(line)) != NULL) {
307       while((de = readdir(dir)) != NULL) {
308 	if (de->d_name[0] != '.' &&
309 	    strlen(de->d_name) < sizeof(line) - 4 &&
310 	    stat((strcpy(line+4, de->d_name), line), &st) == 0 &&
311 	    S_ISREG(st.st_mode))
312 	  addmsg(de->d_name, st.st_size, st.st_mtime, f);
313       }
314       closedir(dir);
315     }
316     if (!f)
317       break;
318     f = 0;
319     strcpy(line, "cur/");
320   }
321 
322   o_msgsz = msgsz;
323   o_nmsg = nmsg;
324   info();
325 }
326 
327 #define doopenlog() openlog("mdpop3d", LOG_PID, LOG_MAIL)
328 
329 #ifdef USE_APOP
330 static char *apoptag;
331 # define IFAPOP(x) x
332 #else
333 # define IFAPOP(x)
334 #endif
335 
336 #ifndef USE_PAM
337 # define IFPAM(x)
338 #else
339 #define IFPAM(x) x
340 
341 static pam_handle_t *pamh;
342 static const char *pam_service = "pop3";
343 
344 struct popdata {
345   const char *pass;
346   char *msg;
347 };
348 
popd_conv(int nmsg,const struct pam_message ** msgp,struct pam_response ** respp,void * appdata)349 static int popd_conv(int nmsg, const struct pam_message **msgp,
350 		     struct pam_response **respp, void *appdata) {
351 
352   int i;
353   struct popdata *d = (struct popdata *) appdata;
354   struct pam_response *resp =
355     (struct pam_response *) malloc(sizeof(struct pam_response) * nmsg);
356 
357   if (!resp)
358     return PAM_BUF_ERR;
359 
360   for (i = 0; i < nmsg; i++) {
361     resp[i].resp_retcode = PAM_SUCCESS;
362     switch (msgp[i]->msg_style) {
363     case PAM_PROMPT_ECHO_ON:
364       resp[i].resp = strdup(user);
365       break;
366     case PAM_PROMPT_ECHO_OFF:
367       resp[i].resp = d && d->pass ? strdup(d->pass) : NULL;
368       break;
369     case PAM_ERROR_MSG:
370       if (msgp[i]->msg && d && !d->msg)
371 	d->msg = strdup(msgp[i]->msg);
372       /* fall through */
373     case PAM_TEXT_INFO:
374       /* ignore it, but pam still wants a NULL response... */
375       resp[i].resp = NULL;
376       break;
377     default:
378       /* Must be an error of some sort... */
379       free(resp);
380       return PAM_CONV_ERR;
381     }
382   }
383   *respp = resp;
384   return PAM_SUCCESS;
385 }
386 
387 static struct pam_conv convstruct = { popd_conv, NULL };
388 
389 #endif
390 
checkpass(char * pass)391 static struct passwd *checkpass(char *pass) {
392   struct passwd *pw;
393 
394 #ifdef USE_PAM
395 
396 
397   int r;
398   struct popdata d;
399   const char *u = NULL;
400   const char **ptr_u = &u;
401 
402   d.pass = pass;
403   d.msg = NULL;
404   convstruct.appdata_ptr = &d;
405 
406 #define PAMOK(x) ((r = x) == PAM_SUCCESS) /* shortcut */
407   /* ok, do all the PAM work.  I don't use PAM_SILENT flag since mdpop3d
408      is able to display at least one pam message. */
409   if (!PAMOK(pam_start(pam_service, user, &convstruct, &pamh))) {
410     syslog(LOG_ERR, "[%s] unable to init PAM: %s",
411 	   rhost, pam_strerror(pamh,r));
412     pw = NULL;
413   }
414   else
415     if (PAMOK(pam_start(pam_service, user, &convstruct, &pamh)) &&
416 	PAMOK(pam_set_item(pamh, PAM_USER, user)) &&
417 	PAMOK(pam_set_item(pamh, PAM_RHOST, rhost)) &&
418 	PAMOK(pam_authenticate(pamh, 0)) &&
419 	PAMOK(pam_acct_mgmt(pamh, 0)) &&
420 	PAMOK(pam_get_item(pamh, PAM_USER, (const void **)ptr_u))) {
421     pw = getpwnam(u && *u ? u : user); /* use username from pam if any */
422     if (!maildir) /* try to get MAILDIR from PAM */
423       maildir = pam_getenv(pamh, maildirenv);
424   }
425   else
426     pw = NULL;
427 #undef PAMOK
428   doopenlog(); /* reopen log since PAM munges it */
429   convstruct.appdata_ptr = NULL; /* do not attempt to use this anymore */
430   if (pw) {
431     if (d.msg)
432       free(d.msg);
433     return pw;
434   }
435   syslog(LOG_INFO, "[%s] login incorrect for [%s]: %s", rhost, user,
436 	 pam_strerror(pamh, r));
437   pam_end(pamh, r == PAM_SUCCESS ? PAM_USER_UNKNOWN : r);
438   pamh = NULL;
439   sleep(3);
440   if (d.msg) {
441     char *p, *n;
442     /* convert multiline message to single line */
443     for(p = d.msg; (n = strchr(p, '\n')) != NULL; p = n + 1)
444       *n = ' ';
445     /* it is safe to use long response -- we use snprintf anyway. */
446     putline(ERR "login incorrect: %s", d.msg);
447     free(d.msg);
448   }
449   else
450     putline(ERR "login incorrect");
451   return NULL;
452 
453 #else /* !USE_PAM */
454 
455   pw = getpwnam(user);
456   endpwent();
457   if (pw != NULL)
458    {
459     if (pw->pw_passwd && strcmp(crypt(pass, pw->pw_passwd), pw->pw_passwd) == 0)
460       return pw;
461     syslog(LOG_INFO, "[%s] login incorrect for [%s]", rhost, user);
462   }
463   else
464     syslog(LOG_INFO, "[%s] user unknown: [%s]", rhost, user);
465   putline(ERR "login incorrect");
466   return NULL;
467 
468 #endif /* USE_PAM */
469 }
470 
auth(char * pass)471 static int auth(char *pass) {
472   struct passwd *pw = checkpass(pass);
473 
474   memset(line, 0, sizeof(line)); /* clear passwd */
475 
476   if (!pw)
477     return -1;
478 
479   if (initgroups(user, pw->pw_gid) != 0 ||
480       setgid(pw->pw_gid) != 0 ||
481       setuid(pw->pw_uid) != 0) {
482     syslog(LOG_ERR, "[%s][%s] unable to set privileges: %m", rhost, user);
483     putline(ERR "unable to setup privileges");
484     return -1;
485   }
486   if (chdir(pw->pw_dir) != 0) {
487     syslog(LOG_ERR, "unable to access home directory %s: %m", pw->pw_dir);
488     putline(ERR "unable to access homedir");
489     return -1;
490   }
491 
492 #ifdef USE_PAM
493   {
494     int r = pam_open_session(pamh, PAM_SILENT);
495     doopenlog(); /* reopen log since PAM munges it */
496     if (r != PAM_SUCCESS) {
497       syslog(LOG_ERR, "[%s][%s]: unable to open session: %s",
498 	     rhost, user, pam_strerror(pamh, r));
499       putline(ERR "unable to open session");
500       pam_end(pamh, r);
501       pamh = NULL;
502       return -1;
503     }
504   }
505 #endif
506 
507   initmaildir();
508 
509   return 0;
510 }
511 
getnum(char ** arg)512 static int getnum(char **arg) {
513   char *a = *arg;
514   int n = 0;
515   if (!a)
516     needarg();
517   else {
518     while (*a >= '0' && *a <= '9')
519       if ((n = n * 10 + (*a++ - '0')) < 0) {
520 	putline(ERR "invalid number");
521 	return -1;
522       }
523     if (a == *arg)
524       putline(ERR "number expected");
525     else if (*a != 0 && *a != ' ')
526       putline(ERR "syntax error");
527     else {
528       while(*a == ' ')
529 	++a;
530       *arg = *a ? a : NULL;
531       return n;
532     }
533   }
534   return -1;
535 }
536 
getmsgno(char ** arg,int expectarg)537 static int getmsgno(char **arg, int expectarg) {
538   int n = getnum(arg);
539   if (n < 0)
540     ;
541   else if (!n || n > nmsg)
542     putline(ERR "no such message");
543   else if (msgp[--n]->flag & F_DELETED)
544     putline(ERR "message deleted");
545   else if (!expectarg && *arg)
546     extraarg();
547   else if (expectarg > 0 && !*arg)
548     needarg();
549   else
550     return n;
551   return -1;
552 }
553 
dolist(int n,int uidl,const char * code)554 static void dolist(int n, int uidl, const char *code) {
555   if (uidl) {
556     char *s = strchr(msgp[n]->fn + 1, ':');
557     if (s)
558       *s = '\0';
559     putmline("%s%d %s", code, n+1, msgp[n]->fn);
560     if (s)
561       *s = ':';
562   }
563   else
564     putmline("%s%d %ld", code, n+1, (long int)msgp[n]->size);
565 }
566 
finalupdate()567 static void finalupdate() {
568   int i;
569   if (cmsg && !nomove)
570     mkdir("cur", 0700);
571   for (i = 0; i < nmsg; ++i) {
572     if (msgp[i]->flag & F_DELETED)
573       unlink(msgfn(i));
574     else if ((msgp[i]->flag & F_NEW) && !nomove) {
575       char fn[sizeof(line)+2]; /* +2 bytes for ":2" */
576       strcpy(fn, "cur/"); strcpy(fn+4, msgp[i]->fn); /* will fit */
577       if (strchr(fn + 5, ':') == NULL)
578 	strcat(fn + 5, ":2");
579       rename(msgfn(i), fn);
580     }
581   }
582   msgp = NULL;
583 }
584 
putmsg(FILE * f,int lim)585 static void putmsg(FILE *f, int lim) {
586   int inh = 1, l = 0, c, s = 0;
587   ok(); /* one packet for OK, and try as little packets as possible for rest */
588   if (debug) syslog(LOG_DEBUG, "out: <message contents>");
589   while((c = getc(f)) != EOF) {
590     if (c == '\r') /* strip CRs from input completely */
591       continue;
592     if (s > sizeof(line) - 7) { /* reserve 7 chars for termination */
593       twrite(line, s);
594       s = 0;
595     }
596     if (c == '\n') {
597       line[s++] = '\r'; line[s++] = '\n';
598       if (!l) inh = 0;
599       else l = 0;
600       if (lim >= 0 && !inh && !lim--)
601 	break;
602     }
603     else {
604       if (!l++ && c == '.')
605 	line[s++] = '.';
606       line[s++] = c;
607     }
608   }
609   if (ferror(f))
610      syslog(LOG_ERR, "[%s][%s] error reading message file: %m",
611             rhost, user);
612   if (l) { line[s++] = '\r'; line[s++] = '\n'; }
613   line[s++] = '.'; line[s++] = '\r'; line[s++] = '\n';
614   twrite(line, s);
615   if (debug) syslog(LOG_DEBUG, "out: .");
616 }
617 
finallog(const char * why)618 static void finallog(const char *why) {
619   syslog(LOG_INFO, "[%s][%s] %s (%d/%ld msgs/bytes left, %d/%ld deleted)",
620          rhost, user, why, cmsg, (long int)msgsz, o_nmsg - cmsg, (long int)o_msgsz - (long int)msgsz);
621 }
622 
die(const char * why)623 static void die(const char *why) {
624   if (commit_on_err && msgp && nmsg != cmsg) {
625     finallog(why);
626     finalupdate();
627   }
628 #ifdef USE_PAM
629   if (pamh) {
630     pam_close_session(pamh, PAM_SILENT);
631     pam_end(pamh, PAM_SUCCESS); /* XXX: Is PAM_SUCCESS appropriate here? */
632   }
633 #endif
634   exit(0);
635 }
636 
sig(int sig)637 static void sig(int sig) {
638   const char *s;
639   switch(sig) {
640     case SIGALRM: s = "timeout"; break;
641     case SIGPIPE: s = "pipe closed"; break;
642     case SIGHUP: s = "hungup"; break;
643     default: s = "got signal"; break;
644   }
645   syslog(LOG_ERR, "[%s] %s, exiting", rhost, s);
646   die(s);
647 }
648 
main(int argc,char ** argv)649 int main(int argc, char **argv) {
650 
651   char *a;
652   int i;
653   int junk = 50;		/* max number of junk commands */
654   int authenticated = 0;
655   int quiet = 0;
656   char *rhostvar = NULL;
657 
658   while((i = getopt(argc, argv,
659 		    "t:cau:r:m:qndR:Vh" IFPAM("s:") IFAPOP("AT:"))) != EOF)
660     switch(i) {
661     case 'r': rhost = optarg; break;
662     case 'R': rhostvar = optarg; break;
663     case 'u': user = optarg; authenticated = 1; break;
664     case 't':
665       if ((timeout = atoi(optarg)) <= 0) timeout = 20;
666       break;
667     case 'c': commit_on_err = 1; break;
668     case 'a': authenticated = 1; break;
669     case 'm': maildir = optarg; break;
670     case 'q': quiet = 1; break;
671     case 'n': nomove = 1; break;
672     case 'd': debug = 1; break;
673     IFPAM(case 's': pam_service = optarg; break;)
674     IFAPOP(case 'A': if (!apoptag) apoptag = (char*)-1; break;)
675     IFAPOP(case 'T': apoptag = optarg; break;)
676     case 'V':
677     case 'h':
678       printf("%s: Maildir POP3 daemon %s" IFPAM(" PAM") IFAPOP(" APOP") "\n\
679 Written by Michael Tokarev <mjt@corpit.ru>\n", argv[0], version);
680       if (i == 'h')
681 	puts("Options:\n\
682  -h - print this help and exit\n\
683  -V - print version info and exit\n\
684  -a - go to pre-auth mode (TRANSACTION state)\n\
685  -u user - pre-auth with username\n\
686  -r rhost - set remote host name/addr\n\
687  -R rhostvar - use ${rhostvar} as remote host name/addr\n\
688  -t tmout - set command timeout in secs\n\
689  -c - commit maildir on error - on communication errors,\n\
690   update maildir just like on QUIT command\n\
691   Note that this violates POP3 but sometimes helps with broken\n\
692   mail clients on slow dialup links.\n\
693  -q - be somewhat syslog-quiet\n\
694  -d - debug mode (passwords will be logged!)\n\
695  -n - do not move messages to cur/\n\
696  -m maildir - cd to this maildir directory\n"
697 IFPAM(" -s service - use this PAM service\n")
698 IFAPOP(" -A - enable APOP command with default host tag\n")
699 IFAPOP(" -T tag - enable APOP with this host tag\n")
700 );
701       return 0;
702     default:
703       return 1;
704     }
705 
706   if (optind != argc) {
707     fprintf(stderr, "%s: extra argument(s) in command line\n", argv[0]);
708     return 1;
709   }
710 
711   if (!authenticated || user)
712     ;
713   else if ((a = getenv("LOGNAME")) != NULL && *a)
714     user = a;
715   else {
716     struct passwd *pw = getpwuid(getuid());
717     if (pw)
718       user = strdup(pw->pw_name);
719     else {
720       snprintf(line, sizeof(line), "#%d", (int)getuid());
721       user = strdup(line);
722     }
723   }
724 
725   if (rhost)
726     ;
727   else if (rhostvar) {
728     if ((rhost = getenv(rhostvar)) == NULL || !*rhost) {
729       fprintf(stderr, "%s: %s variable not set\n", argv[0], rhostvar);
730       return 1;
731     }
732   }
733   else if (((a = getenv("TCPREMOTEIP")) != NULL && *a) ||
734 	   ((a = getenv("REMOTE_HOST")) != NULL && *a))
735     rhost = a;
736   else {
737     struct sockaddr_in sin;
738     i = sizeof(sin);
739     if (getpeername(0, (struct sockaddr*)&sin, &i) == 0)
740       rhost = strdup(inet_ntoa(sin.sin_addr));
741     else if (errno == ENOTSOCK)
742       rhost = "local";
743     else {
744       fprintf(stderr, "%s: unable to get peer name: %m\n", argv[0]);
745       return 1;
746     }
747   }
748   doopenlog();
749   if (!quiet) syslog(LOG_INFO, "connection from %s", rhost);
750   signal(SIGALRM, sig);
751   signal(SIGPIPE, sig);
752 
753   if (!authenticated) {
754 #ifdef USE_APOP
755     if (apoptag) {
756       i = sprintf(line, "<%d.%ld@", (int)getpid(), (long)time(NULL));
757       if (apoptag == (char*)-1)
758 	gethostname(line + i, sizeof(line) - i - 1);
759       else {
760 	if (strlen(apoptag) > 200)
761 	  apoptag[200] = '\0';
762 	strcpy(line + i, apoptag);
763       }
764       i += strlen(line + i);
765       if (i > 200) i = 200;
766       line[i++] = '>'; line[i] = '\0';
767       apoptag = strdup(line);
768       putline(OK "%s ready %s", version, apoptag);
769     }
770     else
771 #endif
772       putline(OK "%s ready", version);
773   }
774   else
775     initmaildir();
776 
777   for (;;) {
778 
779     alarm(timeout);
780     if (!fgets(line,sizeof(line),stdin)) {
781       alarm(0);
782       if (ferror(stdin))
783 	syslog(LOG_ERR, "[%s] unable to read from client: %m", rhost);
784       else
785 	syslog(LOG_ERR, "[%s] end of line from client", rhost);
786       die("client read error");
787     }
788     alarm(0);
789     i = strlen(line);
790     if (i && line[i-1] == '\n')
791       line[--i] = '\0';
792     if (i && line[i-1] == '\r')
793       line[--i] = '\0';
794     if (debug) {
795       /* do not log user's password */
796       if ((line[0] == 'p' || line[0] == 'P') &&
797 	  (line[1] == 'a' || line[1] == 'A') &&
798 	  (line[2] == 's' || line[2] == 'S') &&
799 	  (line[3] == 's' || line[3] == 'S') &&
800 	  (line[4] == ' '))
801 	syslog(LOG_DEBUG, "in: %.5s<hidden>", line);
802       else
803 	syslog(LOG_DEBUG, "in: %s", line);
804     }
805     a = line;
806     while(*a && *a != ' ') {
807       if (*a >= 'A' && *a <= 'Z')
808 	*a = *a - 'A' + 'a';
809       ++a;
810     }
811     if (*a == ' ') {
812       *a++ = '\0';
813       while(*a == ' ')
814 	++a;
815     }
816     if (!*a)
817       a = NULL;
818 
819 
820     if (!strcmp(line, "quit")) {
821       if (authenticated) {
822 	if (!quiet)
823           finallog("client quit");
824 	finalupdate();
825 	info();
826       }
827       else {
828 	if (!quiet) syslog(LOG_INFO, "[%s] client quit", rhost);
829 	ok();
830       }
831       break;
832     }
833 
834     else if (!strcmp(line, "noop")) {
835       if (authenticated) info();
836       else ok();
837       --junk;
838     }
839 
840     else if (!strcmp(line, "user")) {
841       if (user)
842 	putline(ERR "user already specified"), --junk;
843       else if (!a)
844 	needarg();
845       else if ((user = strdup(a)) == NULL)
846 	putline(ERR "unable to allocate memory");
847       else
848 	ok();
849     }
850 
851     else if (!strcmp(line, "pass")) {
852       if (!user)
853 	putline(ERR "user first"), --junk;
854       else if (authenticated)
855 	putline(ERR "already authenticated"), --junk;
856       else if (!a)
857 	needarg(), --junk;
858 #ifdef USE_APOP
859       else if (strlen(a) > 4 && memcmp(a, "APOP ", 5) == 0) {
860         syslog(LOG_ERR, "[%s][%s] attempt to use invalid passwd beginning with \"APOP \"",
861                rhost, user);
862         putline(ERR "invalid passwd");
863         break;
864       }
865 #endif
866       else if (auth(a) != 0)
867 	break;
868       else
869 	authenticated = 1;
870     }
871 
872 #ifdef USE_APOP
873     else if (apoptag && !strcmp(line, "apop")) {
874       char *t;
875       if (!a || (t = strchr(a, ' ')) == NULL)
876 	needarg(), --junk;
877       else if (strchr(t + 1, ' '))
878 	extraarg(), --junk;
879       else if (user)
880 	putline(ERR "user already specified"), --junk;
881       else {
882 	*t++ = '\0';
883 	user = strdup(a);
884 	for (a = t; *t; ++t)
885 	  if ((*t < '0' || *t > '9') && (*t < 'a' || *t > 'f'))
886 	    break;
887 	if (*t || t - a != 32) {
888 	  syslog(LOG_ERR, "[%s][%s] invalid digest for APOP auth command",
889 		 rhost, user);
890 	  putline(ERR "invalid passwd md5 digest");
891 	  break;
892 	}
893 	else {
894 	  /* we use special password here: APOP mdhash apoptag */
895 	  /* it is safe to use line here (`a' points inside of it) */
896 	  sprintf(line, "APOP %s %s", a, apoptag);
897 	  if (auth(line) != 0)
898 	    break;
899 	  authenticated = 1;
900 	}
901       }
902     }
903 #endif
904 
905     else if (!strcmp(line, "list") || !strcmp(line, "uidl")) {
906       int uidl = strcmp(line, "uidl") == 0;
907       if (!authenticated)
908 	needauth(), --junk;
909       else if (!a) {
910 	putmline("+OK");
911 	for(i = 0; i < nmsg; ++i)
912 	  if (!(msgp[i]->flag & F_DELETED))
913 	    dolist(i, uidl, "");
914 	putmline(".");
915 	flushmline();
916       }
917       else if ((i = getmsgno(&a, 0)) < 0)
918 	--junk;
919       else {
920 	dolist(i, uidl, OK);
921 	flushmline();
922       }
923     }
924 
925     else if (!strcmp(line, "stat")) {
926       if (!authenticated)
927 	needauth(), --junk;
928       else if (a)
929 	extraarg(), --junk;
930       else
931 	putline(OK "%d %ld", cmsg, (long int)msgsz);
932     }
933 
934     else if (!strcmp(line, "top") || !strcmp(line, "retr")) {
935       int lim = (line[0] == 't') ? 0 : -1; /* -1 - unlimited, 0 - to be */
936       FILE *f;
937       if (!authenticated)
938 	needauth(), --junk;
939       else if ((i = getmsgno(&a, !lim)) < 0)
940 	--junk;
941       else if (lim && a)
942 	extraarg(), --junk;
943       else if (a && (lim = getnum(&a)) < 0)
944 	--junk;
945       else if (a)
946 	extraarg(), --junk;
947       else if ((f = fopen(msgfn(i), "r")) == NULL)
948 	putline(ERR "no such message");
949       else
950 	putmsg(f, lim);
951     }
952 
953     else if (!strcmp(line, "dele")) {
954       if (!authenticated)
955 	needauth(), --junk;
956       else if ((i = getmsgno(&a, 0)) >= 0) {
957 	msgp[i]->flag |= F_DELETED;
958 	msgsz -= msgp[i]->size;
959 	--cmsg;
960 	ok();
961       }
962     }
963 
964     else if (!strcmp(line, "rset")) {
965       if (!authenticated)
966 	needauth(), --junk;
967       else if (a)
968 	extraarg(), --junk;
969       else {
970 	cmsg = nmsg; msgsz = 0;
971 	for (i = nmsg - 1; i >= 0; --i) {
972 	  msgp[i]->flag &= ~F_DELETED;
973 	  msgsz += msgp[i]->size;
974 	}
975 	info();
976       }
977     }
978 
979     else if (!strcmp(line, "capa")) {
980       if (a)
981 	extraarg();
982       else
983 	putline(OK "\r\nTOP\r\nUIDL\r\nUSER\r\n\r\n.");
984       --junk;
985     }
986 
987     else
988       putline(ERR "command not recognized"), --junk;
989 
990     if (!junk) {
991       syslog(LOG_NOTICE, "[%s][%s] too many junk commands, giving up",
992 	     rhost, authenticated ? user : "(none)");
993       putline(ERR "too many junk received");
994       break;
995     }
996 
997   }
998 
999 #ifdef USE_PAM
1000   if (pamh) {
1001     pam_close_session(pamh, PAM_SILENT);
1002     pam_end(pamh, PAM_SUCCESS);
1003   }
1004 #endif
1005 
1006   return 0;
1007 }
1008