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