1/*
2 * Copyright (c) 1997-2004 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "push_locl.h"
35RCSID("$Id$");
36
37#if defined(_AIX) && defined(STAT)
38/*
39 * AIX defines STAT to 1 in sys/dir.h
40 */
41#  undef STAT
42#endif
43
44#ifdef KRB5
45static int use_v5 = -1;
46static krb5_context context;
47#endif
48
49static char *port_str;
50static int verbose_level;
51static int do_fork;
52static int do_leave;
53static int do_version;
54static int do_help;
55static int do_from;
56static int do_count;
57static char *header_str;
58
59struct getargs args[] = {
60#ifdef KRB5
61    { "krb5",	'5', arg_flag,		&use_v5,	"Use Kerberos V5",
62      NULL },
63#endif
64    { "verbose",'v', arg_counter,	&verbose_level,	"Verbose",
65      NULL },
66    { "fork",	'f', arg_flag,		&do_fork,	"Fork deleting proc",
67      NULL },
68    { "leave",	'l', arg_flag,		&do_leave,	"Leave mail on server",
69      NULL },
70    { "port",	'p', arg_string,	&port_str,	"Use this port",
71      "number-or-service" },
72    { "from",	 0,  arg_flag,		&do_from,	"Behave like from",
73      NULL },
74    { "headers", 0,  arg_string,	&header_str,	"Headers to print", NULL },
75    { "count", 'c',  arg_flag,		&do_count,	"Print number of messages", NULL},
76    { "version", 0,  arg_flag,		&do_version,	"Print version",
77      NULL },
78    { "help",	 0,  arg_flag,		&do_help,	NULL,
79      NULL }
80
81};
82
83static void
84usage (int ret)
85{
86    arg_printusage (args,
87		    sizeof(args) / sizeof(args[0]),
88		    NULL,
89		    "[[{po:username[@hostname] | hostname[:username]}] ...] "
90		    "filename");
91    exit (ret);
92}
93
94static int
95do_connect (const char *hostname, int port, int nodelay)
96{
97    struct addrinfo *ai, *a;
98    struct addrinfo hints;
99    int error;
100    int s = -1;
101    char portstr[NI_MAXSERV];
102
103    memset (&hints, 0, sizeof(hints));
104    hints.ai_socktype = SOCK_STREAM;
105    hints.ai_protocol = IPPROTO_TCP;
106
107    snprintf (portstr, sizeof(portstr), "%u", ntohs(port));
108
109    error = getaddrinfo (hostname, portstr, &hints, &ai);
110    if (error)
111	errx (1, "getaddrinfo(%s): %s", hostname, gai_strerror(error));
112
113    for (a = ai; a != NULL; a = a->ai_next) {
114	s = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
115	if (s < 0)
116	    continue;
117	if (connect (s, a->ai_addr, a->ai_addrlen) < 0) {
118	    warn ("connect(%s)", hostname);
119 	    close (s);
120 	    continue;
121	}
122	break;
123    }
124    freeaddrinfo (ai);
125    if (a == NULL) {
126	warnx ("failed to contact %s", hostname);
127	return -1;
128    }
129
130    if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
131		  (void *)&nodelay, sizeof(nodelay)) < 0)
132	err (1, "setsockopt TCP_NODELAY");
133    return s;
134}
135
136typedef enum { INIT = 0, GREET, USER, PASS, STAT, RETR, TOP,
137	       DELE, XDELE, QUIT} pop_state;
138
139static char *pop_state_string[] = {
140    "INIT", "GREET", "USER", "PASS", "STAT", "RETR", "TOP",
141    "DELE", "XDELE", "QUIT"
142};
143
144#define PUSH_BUFSIZ 65536
145
146#define STEP 16
147
148struct write_state {
149    struct iovec *iovecs;
150    size_t niovecs, maxiovecs, allociovecs;
151    int fd;
152};
153
154static void
155write_state_init (struct write_state *w, int fd)
156{
157#ifdef UIO_MAXIOV
158    w->maxiovecs = UIO_MAXIOV;
159#else
160    w->maxiovecs = 16;
161#endif
162    w->allociovecs = min(STEP, w->maxiovecs);
163    w->niovecs = 0;
164    w->iovecs = emalloc(w->allociovecs * sizeof(*w->iovecs));
165    w->fd = fd;
166}
167
168static void
169write_state_add (struct write_state *w, void *v, size_t len)
170{
171    if(w->niovecs == w->allociovecs) {
172	if(w->niovecs == w->maxiovecs) {
173	    if(writev (w->fd, w->iovecs, w->niovecs) < 0)
174		err(1, "writev");
175	    w->niovecs = 0;
176	} else {
177	    w->allociovecs = min(w->allociovecs + STEP, w->maxiovecs);
178	    w->iovecs = erealloc (w->iovecs,
179				  w->allociovecs * sizeof(*w->iovecs));
180	}
181    }
182    w->iovecs[w->niovecs].iov_base = v;
183    w->iovecs[w->niovecs].iov_len  = len;
184    ++w->niovecs;
185}
186
187static void
188write_state_flush (struct write_state *w)
189{
190    if (w->niovecs) {
191	if (writev (w->fd, w->iovecs, w->niovecs) < 0)
192	    err (1, "writev");
193	w->niovecs = 0;
194    }
195}
196
197static void
198write_state_destroy (struct write_state *w)
199{
200    free (w->iovecs);
201}
202
203static int
204doit(int s,
205     const char *host,
206     const char *user,
207     const char *outfilename,
208     const char *header_str,
209     int leavep,
210     int verbose,
211     int forkp)
212{
213    int ret;
214    char out_buf[PUSH_BUFSIZ];
215    int out_len = 0;
216    char *in_buf;
217    size_t in_buf_size;
218    size_t in_len = 0;
219    char *in_ptr;
220    pop_state state = INIT;
221    unsigned count = 0, bytes;
222    unsigned asked_for = 0, retrieved = 0, asked_deleted = 0, deleted = 0;
223    unsigned sent_xdele = 0;
224    int out_fd;
225    char from_line[128];
226    size_t from_line_length;
227    time_t now;
228    struct write_state write_state;
229    unsigned int numheaders = 1;
230    char **headers = NULL;
231    int i;
232    char *tmp = NULL;
233
234    in_buf = emalloc(PUSH_BUFSIZ + 1);
235    in_ptr = in_buf;
236    in_buf_size = PUSH_BUFSIZ;
237
238    if (do_from) {
239	char *tmp2;
240
241	tmp2 = tmp = estrdup(header_str);
242
243	out_fd = -1;
244	if (verbose)
245	    fprintf (stderr, "%s@%s\n", user, host);
246	while (*tmp != '\0') {
247	    tmp = strchr(tmp, ',');
248	    if (tmp == NULL)
249		break;
250	    tmp++;
251	    numheaders++;
252	}
253
254	headers = emalloc(sizeof(char *) * (numheaders + 1));
255	for (i = 0; i < numheaders; i++) {
256	    headers[i] = strtok_r(tmp2, ",", &tmp2);
257	}
258	headers[numheaders] = NULL;
259    } else {
260	out_fd = open(outfilename, O_WRONLY | O_APPEND | O_CREAT, 0666);
261	if (out_fd < 0)
262	    err (1, "open %s", outfilename);
263	if (verbose)
264	    fprintf (stderr, "%s@%s -> %s\n", user, host, outfilename);
265    }
266
267    now = time(NULL);
268    from_line_length = snprintf (from_line, sizeof(from_line),
269				 "From %s %s", "push", ctime(&now));
270    if (from_line_length < 0 || from_line_length > sizeof(from_line))
271	errx (1, "snprintf failed");
272
273    out_len = snprintf (out_buf, sizeof(out_buf),
274			"USER %s\r\nPASS hej\r\nSTAT\r\n",
275			user);
276    if (out_len < 0 || out_len > sizeof(out_buf))
277	errx (1, "snprintf failed");
278    if (net_write (s, out_buf, out_len) != out_len)
279	err (1, "write");
280    if (verbose > 1)
281	fprintf (stderr, "%s", out_buf);
282
283    if (!do_from)
284	write_state_init (&write_state, out_fd);
285
286    while(state != QUIT) {
287	fd_set readset, writeset;
288
289	FD_ZERO(&readset);
290	FD_ZERO(&writeset);
291	if (s >= FD_SETSIZE)
292	    errx (1, "fd too large");
293	FD_SET(s,&readset);
294
295	if (verbose > 1)
296	    fprintf (stderr, "state: %s count: %d asked_for: %d "
297		     "retrieved: %d asked_deleted: %d\n",
298		     pop_state_string[state],
299		     count, asked_for, retrieved, asked_deleted);
300
301	if (((state == STAT || state == RETR || state == TOP)
302	     && asked_for < count)
303	    || (state == XDELE && !sent_xdele)
304	    || (state == DELE && asked_deleted < count))
305	    FD_SET(s,&writeset);
306	ret = select (s + 1, &readset, &writeset, NULL, NULL);
307	if (ret < 0) {
308	    if (errno == EAGAIN)
309		continue;
310	    else
311		err (1, "select");
312	}
313
314	if (FD_ISSET(s, &readset)) {
315	    char *beg, *p;
316	    size_t rem;
317	    int blank_line = 0;
318
319	    if(in_len >= in_buf_size) {
320		char *tmp = erealloc(in_buf, in_buf_size + PUSH_BUFSIZ + 1);
321		in_ptr = tmp + (in_ptr - in_buf);
322		in_buf = tmp;
323		in_buf_size += PUSH_BUFSIZ;
324	    }
325
326	    ret = read (s, in_ptr, in_buf_size - in_len);
327	    if (ret < 0)
328		err (1, "read");
329	    else if (ret == 0)
330		errx (1, "EOF during read");
331
332	    in_len += ret;
333	    in_ptr += ret;
334	    *in_ptr = '\0';
335
336	    beg = in_buf;
337	    rem = in_len;
338	    while(rem > 1
339		  && (p = strstr(beg, "\r\n")) != NULL) {
340		if (state == TOP) {
341		    char *copy = beg;
342
343		    for (i = 0; i < numheaders; i++) {
344			size_t len;
345
346			len = min(p - copy + 1, strlen(headers[i]));
347			if (strncasecmp(copy, headers[i], len) == 0) {
348			    fprintf (stdout, "%.*s\n", (int)(p - copy), copy);
349			}
350		    }
351		    if (beg[0] == '.' && beg[1] == '\r' && beg[2] == '\n') {
352			if (numheaders > 1)
353			    fprintf (stdout, "\n");
354			state = STAT;
355			if (++retrieved == count) {
356			    state = QUIT;
357			    net_write (s, "QUIT\r\n", 6);
358			    if (verbose > 1)
359				fprintf (stderr, "QUIT\r\n");
360			}
361		    }
362		    rem -= p - beg + 2;
363		    beg = p + 2;
364		} else if (state == RETR) {
365		    char *copy = beg;
366		    if (beg[0] == '.') {
367			if (beg[1] == '\r' && beg[2] == '\n') {
368			    if(!blank_line)
369				write_state_add(&write_state, "\n", 1);
370			    state = STAT;
371			    rem -= p - beg + 2;
372			    beg = p + 2;
373			    if (++retrieved == count) {
374				write_state_flush (&write_state);
375				if (fsync (out_fd) < 0)
376				    err (1, "fsync");
377				close(out_fd);
378				if (leavep) {
379				    state = QUIT;
380				    net_write (s, "QUIT\r\n", 6);
381				    if (verbose > 1)
382					fprintf (stderr, "QUIT\r\n");
383				} else {
384				    if (forkp) {
385					pid_t pid;
386
387					pid = fork();
388					if (pid < 0)
389					    warn ("fork");
390					else if(pid != 0) {
391					    if(verbose)
392						fprintf (stderr,
393							 "(exiting)");
394					    return 0;
395					}
396				    }
397
398				    state = XDELE;
399				    if (verbose)
400					fprintf (stderr, "deleting... ");
401				}
402			    }
403			    continue;
404			} else
405			    ++copy;
406		    }
407		    *p = '\n';
408		    if(blank_line &&
409		       strncmp(copy, "From ", min(p - copy + 1, 5)) == 0)
410			write_state_add(&write_state, ">", 1);
411		    write_state_add(&write_state, copy, p - copy + 1);
412		    blank_line = (*copy == '\n');
413		    rem -= p - beg + 2;
414		    beg = p + 2;
415		} else if (rem >= 3 && strncmp (beg, "+OK", 3) == 0) {
416		    if (state == STAT) {
417			if (!do_from)
418			    write_state_add(&write_state,
419					    from_line, from_line_length);
420			blank_line = 0;
421			if (do_from)
422			    state = TOP;
423			else
424			    state = RETR;
425		    } else if (state == XDELE) {
426			state = QUIT;
427			net_write (s, "QUIT\r\n", 6);
428			if (verbose > 1)
429			    fprintf (stderr, "QUIT\r\n");
430			break;
431		    } else if (state == DELE) {
432			if (++deleted == count) {
433			    state = QUIT;
434			    net_write (s, "QUIT\r\n", 6);
435			    if (verbose > 1)
436				fprintf (stderr, "QUIT\r\n");
437			    break;
438			}
439		    } else if (++state == STAT) {
440			if(sscanf (beg + 4, "%u %u", &count, &bytes) != 2)
441			    errx(1, "Bad STAT-line: %.*s", (int)(p - beg), beg);
442			if (verbose) {
443			    fprintf (stderr, "%u message(s) (%u bytes). "
444				     "fetching... ",
445				     count, bytes);
446			    if (do_from)
447				fprintf (stderr, "\n");
448			} else if (do_count) {
449			    fprintf (stderr, "%u message(s) (%u bytes).\n",
450				     count, bytes);
451			}
452			if (count == 0) {
453			    state = QUIT;
454			    net_write (s, "QUIT\r\n", 6);
455			    if (verbose > 1)
456				fprintf (stderr, "QUIT\r\n");
457			    break;
458			}
459		    }
460
461		    rem -= p - beg + 2;
462		    beg = p + 2;
463		} else {
464		    if(state == XDELE) {
465			state = DELE;
466			rem -= p - beg + 2;
467			beg = p + 2;
468		    } else
469			errx (1, "Bad response: %.*s", (int)(p - beg), beg);
470		}
471	    }
472	    if (!do_from)
473		write_state_flush (&write_state);
474
475	    memmove (in_buf, beg, rem);
476	    in_len = rem;
477	    in_ptr = in_buf + rem;
478	}
479	if (FD_ISSET(s, &writeset)) {
480	    if ((state == STAT && !do_from) || state == RETR)
481		out_len = snprintf (out_buf, sizeof(out_buf),
482				    "RETR %u\r\n", ++asked_for);
483	    else if ((state == STAT && do_from) || state == TOP)
484		out_len = snprintf (out_buf, sizeof(out_buf),
485				    "TOP %u 0\r\n", ++asked_for);
486	    else if(state == XDELE) {
487		out_len = snprintf(out_buf, sizeof(out_buf),
488				   "XDELE %u %u\r\n", 1, count);
489		sent_xdele++;
490	    }
491	    else if(state == DELE)
492		out_len = snprintf (out_buf, sizeof(out_buf),
493				    "DELE %u\r\n", ++asked_deleted);
494	    if (out_len < 0 || out_len > sizeof(out_buf))
495		errx (1, "snprintf failed");
496	    if (net_write (s, out_buf, out_len) != out_len)
497		err (1, "write");
498	    if (verbose > 1)
499		fprintf (stderr, "%s", out_buf);
500	}
501    }
502    if (verbose)
503	fprintf (stderr, "Done\n");
504    if (do_from) {
505	free (tmp);
506	free (headers);
507    } else {
508	write_state_destroy (&write_state);
509    }
510    return 0;
511}
512
513#ifdef KRB5
514static int
515do_v5 (const char *host,
516       int port,
517       const char *user,
518       const char *filename,
519       const char *header_str,
520       int leavep,
521       int verbose,
522       int forkp)
523{
524    krb5_error_code ret;
525    krb5_auth_context auth_context = NULL;
526    krb5_principal server;
527    int s;
528
529    s = do_connect (host, port, 1);
530    if (s < 0)
531	return 1;
532
533    ret = krb5_sname_to_principal (context,
534				   host,
535				   "pop",
536				   KRB5_NT_SRV_HST,
537				   &server);
538    if (ret) {
539	warnx ("krb5_sname_to_principal: %s",
540	       krb5_get_err_text (context, ret));
541	return 1;
542    }
543
544    ret = krb5_sendauth (context,
545			 &auth_context,
546			 &s,
547			 "KPOPV1.0",
548			 NULL,
549			 server,
550			 0,
551			 NULL,
552			 NULL,
553			 NULL,
554			 NULL,
555			 NULL,
556			 NULL);
557    krb5_free_principal (context, server);
558    if (ret) {
559	warnx ("krb5_sendauth: %s",
560	       krb5_get_err_text (context, ret));
561	return 1;
562    }
563    return doit (s, host, user, filename, header_str, leavep, verbose, forkp);
564}
565#endif
566
567#ifdef HESIOD
568
569#ifdef HESIOD_INTERFACES
570
571static char *
572hesiod_get_pobox (const char **user)
573{
574    void *context;
575    struct hesiod_postoffice *hpo;
576    char *ret = NULL;
577
578    if(hesiod_init (&context) != 0)
579	err (1, "hesiod_init");
580
581    hpo = hesiod_getmailhost (context, *user);
582    if (hpo == NULL) {
583	warn ("hesiod_getmailhost %s", *user);
584    } else {
585	if (strcasecmp(hpo->hesiod_po_type, "pop") != 0)
586	    errx (1, "Unsupported po type %s", hpo->hesiod_po_type);
587
588	ret = estrdup(hpo->hesiod_po_host);
589	*user = estrdup(hpo->hesiod_po_name);
590	hesiod_free_postoffice (context, hpo);
591    }
592    hesiod_end (context);
593    return ret;
594}
595
596#else /* !HESIOD_INTERFACES */
597
598static char *
599hesiod_get_pobox (const char **user)
600{
601    char *ret = NULL;
602    struct hes_postoffice *hpo;
603
604    hpo = hes_getmailhost (*user);
605    if (hpo == NULL) {
606	warn ("hes_getmailhost %s", *user);
607    } else {
608	if (strcasecmp(hpo->po_type, "pop") != 0)
609	    errx (1, "Unsupported po type %s", hpo->po_type);
610
611	ret = estrdup(hpo->po_host);
612	*user = estrdup(hpo->po_name);
613    }
614    return ret;
615}
616
617#endif /* HESIOD_INTERFACES */
618
619#endif /* HESIOD */
620
621static char *
622get_pobox (const char **user)
623{
624    char *ret = NULL;
625
626#ifdef HESIOD
627    ret = hesiod_get_pobox (user);
628#endif
629
630    if (ret == NULL)
631	ret = getenv("MAILHOST");
632    if (ret == NULL)
633	errx (1, "MAILHOST not set");
634    return ret;
635}
636
637static void
638parse_pobox (char *a0, const char **host, const char **user)
639{
640    const char *h, *u;
641    char *p;
642    int po = 0;
643
644    if (a0 == NULL) {
645
646	*user = getenv ("USERNAME");
647	if (*user == NULL) {
648	    struct passwd *pwd = getpwuid (getuid ());
649
650	    if (pwd == NULL)
651		errx (1, "Who are you?");
652	    *user = estrdup (pwd->pw_name);
653	}
654	*host = get_pobox (user);
655	return;
656    }
657
658    /* if the specification starts with po:, remember this information */
659    if(strncmp(a0, "po:", 3) == 0) {
660	a0 += 3;
661	po++;
662    }
663    /* if there is an `@', the hostname is after it, otherwise at the
664       beginning of the string */
665    p = strchr(a0, '@');
666    if(p != NULL) {
667	*p++ = '\0';
668	h = p;
669    } else {
670	h = a0;
671    }
672    /* if there is a `:', the username comes before it, otherwise at
673       the beginning of the string */
674    p = strchr(a0, ':');
675    if(p != NULL) {
676	*p++ = '\0';
677	u = p;
678    } else {
679	u = a0;
680    }
681    if(h == u) {
682	/* some inconsistent compatibility with various mailers */
683	if(po) {
684	    h = get_pobox (&u);
685	} else {
686	    u = get_default_username ();
687	    if (u == NULL)
688		errx (1, "Who are you?");
689	}
690    }
691    *host = h;
692    *user = u;
693}
694
695int
696main(int argc, char **argv)
697{
698    int port = 0;
699    int optind = 0;
700    int ret = 1;
701    const char *host, *user, *filename = NULL;
702    char *pobox = NULL;
703
704    setprogname (argv[0]);
705
706#ifdef KRB5
707    {
708	krb5_error_code ret;
709
710	ret = krb5_init_context (&context);
711	if (ret)
712	    errx (1, "krb5_init_context failed: %d", ret);
713    }
714#endif
715
716    if (getarg (args, sizeof(args) / sizeof(args[0]), argc, argv,
717		&optind))
718	usage (1);
719
720    argc -= optind;
721    argv += optind;
722
723    if (do_help)
724	usage (0);
725
726    if (do_version) {
727	print_version(NULL);
728	return 0;
729    }
730
731    if (do_from && header_str == NULL)
732	header_str = "From:";
733    else if (header_str != NULL)
734	do_from = 1;
735
736    if (do_from) {
737	if (argc == 0)
738	    pobox = NULL;
739	else if (argc == 1)
740	    pobox = argv[0];
741	else
742	    usage (1);
743    } else {
744	if (argc == 1) {
745	    filename = argv[0];
746	    pobox    = NULL;
747	} else if (argc == 2) {
748	    filename = argv[1];
749	    pobox    = argv[0];
750	} else
751	    usage (1);
752    }
753
754    if (port_str) {
755	struct servent *s = roken_getservbyname (port_str, "tcp");
756
757	if (s)
758	    port = s->s_port;
759	else {
760	    char *ptr;
761
762	    port = strtol (port_str, &ptr, 10);
763	    if (port == 0 && ptr == port_str)
764		errx (1, "Bad port `%s'", port_str);
765	    port = htons(port);
766	}
767    }
768    if (port == 0) {
769#ifdef KRB5
770	port = krb5_getportbyname (context, "kpop", "tcp", 1109);
771#else
772#error must define KRB5
773#endif
774    }
775
776    parse_pobox (pobox, &host, &user);
777
778#ifdef KRB5
779    if (ret && use_v5) {
780	ret = do_v5 (host, port, user, filename, header_str,
781		     do_leave, verbose_level, do_fork);
782    }
783#endif
784    return ret;
785}
786