1 /* GNU Mailutils -- a suite of utilities for electronic mail
2    Copyright (C) 2003-2021 Free Software Foundation, Inc.
3 
4    GNU Mailutils is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    GNU Mailutils is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with GNU Mailutils.  If not, see <http://www.gnu.org/licenses/>.  */
16 
17 #if defined(HAVE_CONFIG_H)
18 # include <config.h>
19 #endif
20 
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/stat.h>
24 #include <pwd.h>
25 #include <grp.h>
26 #include <unistd.h>
27 #include <mailutils/mailutils.h>
28 #include <mailutils/tls.h>
29 #include "mailutils/cli.h"
30 #include <muaux.h>
31 #ifdef HAVE_TERMIOS_H
32 # include <termios.h>
33 #endif
34 #include <sys/ioctl.h>
35 
36 static int reverse_order;
37 static int preserve_mail;
38 static int emacs_mode;
39 static int uidl_option;
40 static int verbose_option;
41 static int ignore_errors;
42 static char *program_id_option;
43 static size_t max_messages_option;
44 static int notify;
45 static int progress_meter_option;
46 
47   /* These bits tell what to do when an error occurs: */
48 #define ONERROR_SKIP     0x01  /* Skip to the next message */
49 #define ONERROR_DELETE   0x02  /* Delete the source message */
50 #define ONERROR_COUNT    0x04  /* Count it as processed */
51 
52 static int onerror_flags;
53 
54 size_t msg_count = 0;         /* Number of processed messages */
55 size_t get_err_count = 0;     /* Number of message retrieval errors */
56 size_t app_err_count = 0;     /* Number of message appending errors */
57 
58 enum set_ownership_mode
59   {
60     copy_owner_id,
61     copy_owner_name,
62     set_owner_id,
63     set_owner_name
64   };
65 #define SET_OWNERSHIP_MAX 4
66 
67 struct user_id
68 {
69   uid_t uid;
70   gid_t gid;
71 };
72 
73 struct set_ownership_method
74 {
75   enum set_ownership_mode mode;
76   union
77   {
78     char *name;
79     struct user_id id;
80   } owner;
81 };
82 
83 static struct set_ownership_method so_methods[SET_OWNERSHIP_MAX];
84 static int so_method_num;
85 
86 struct set_ownership_method *
get_next_so_method()87 get_next_so_method ()
88 {
89   if (so_method_num == MU_ARRAY_SIZE (so_methods))
90     {
91       mu_error (_("ownership method table overflow"));
92       exit (1);
93     }
94   return so_methods + so_method_num++;
95 }
96 
97 mu_kwd_t method_kwd[] = {
98   { "copy-id", copy_owner_id },
99   { "copy-name", copy_owner_name },
100   { "set-name", set_owner_name },
101   { "user", set_owner_name },
102   { "set-id", set_owner_id },
103   { NULL }
104 };
105 
106 static int
set_mailbox_ownership(const char * str)107 set_mailbox_ownership (const char *str)
108 {
109   if (strcmp (str, "clear") == 0)
110     so_method_num = 0;
111   else
112     {
113       int code;
114       char *p;
115       size_t len = strcspn (str, "=");
116       struct set_ownership_method *meth;
117 
118       if (mu_kwd_xlat_name_len (method_kwd, str, len, &code))
119 	{
120 	  mu_error (_("invalid ownership method: %s"), str);
121 	  return 1;
122 	}
123 
124       meth = get_next_so_method ();
125       meth->mode = code;
126       switch (meth->mode)
127 	{
128 	case copy_owner_id:
129 	case copy_owner_name:
130 	  break;
131 
132 	case set_owner_id:
133 	  if (!str[len])
134 	    {
135 	      mu_error (_("ownership method %s requires value"), str);
136 	      return 1;
137 	    }
138 	  str += len + 1;
139 	  meth->owner.id.uid = strtoul (str, &p, 0);
140 	  if (*p)
141 	    {
142 	      if (*p == ':')
143 		{
144 		  str = p + 1;
145 		  meth->owner.id.gid = strtoul (str, &p, 0);
146 		  if (*p)
147 		    {
148 		      mu_error (_("expected gid number, but found %s"), str);
149 		      return 1;
150 		    }
151 		}
152 	      else
153 		{
154 		  mu_error (_("expected uid number, but found %s"), str);
155 		  return 1;
156 		}
157 	    }
158 	  else
159 	    meth->owner.id.gid = (gid_t) -1;
160 	  break;
161 
162 	case set_owner_name:
163 	  if (!str[len])
164 	    {
165 	      mu_error (_("ownership method %s requires value"), str);
166 	      return 1;
167 	    }
168 	  meth->owner.name = mu_strdup (str + len + 1);
169 	}
170     }
171   return 0;
172 }
173 
174 static int
set_mailbox_ownership_list(char const * str)175 set_mailbox_ownership_list (char const *str)
176 {
177   if (!strchr (str, ','))
178     return set_mailbox_ownership (str);
179   else
180     {
181       struct mu_wordsplit ws;
182       size_t i;
183 
184       ws.ws_delim = ",";
185       if (mu_wordsplit (str, &ws, MU_WRDSF_DEFFLAGS|MU_WRDSF_DELIM))
186 	{
187 	  mu_error (_("cannot parse %s: %s"),
188 		    str, mu_wordsplit_strerror (&ws));
189 	  return 1;
190 	}
191 
192       for (i = 0; i < ws.ws_wordc; i++)
193 	if (set_mailbox_ownership (ws.ws_wordv[i]))
194 	  return 1;
195       mu_wordsplit_free (&ws);
196       return 0;
197     }
198 }
199 
200 static int
set_onerror_action(void * item,void * data)201 set_onerror_action (void *item, void *data)
202 {
203   char *str = item;
204 
205   if (strcmp (str, "abort") == 0)
206     onerror_flags = 0;
207   else
208     {
209       static struct mu_kwd onerror_kw[] = {
210 	{ "skip", ONERROR_SKIP },
211 	{ "delete", ONERROR_DELETE },
212 	{ "count", ONERROR_COUNT },
213 	{ NULL }
214       };
215       int flag, clr = 0;
216 
217       if (strncmp (str, "no", 2) == 0)
218 	{
219 	  clr = 1;
220 	  str += 2;
221 	}
222       if (mu_kwd_xlat_name (onerror_kw, str, &flag))
223 	{
224 	  mu_error (_("unknown keyword: %s"), str);
225 	  return MU_ERR_FAILURE;
226 	}
227       if (clr)
228 	onerror_flags &= ~flag;
229       else
230 	onerror_flags |= flag;
231     }
232   return 0;
233 }
234 
235 static int
set_onerror_actions(char const * str)236 set_onerror_actions (char const *str)
237 {
238   mu_list_t list;
239   int rc;
240 
241   mu_list_create (&list);
242   mu_list_set_destroy_item (list, mu_list_free_item);
243   mu_string_split (str, ",", list);
244   rc = mu_list_foreach (list, set_onerror_action, NULL);
245   mu_list_destroy (&list);
246   return rc;
247 }
248 
249 static void
cli_mailbox_ownership(struct mu_parseopt * po,struct mu_option * opt,char const * arg)250 cli_mailbox_ownership (struct mu_parseopt *po, struct mu_option *opt,
251 		       char const *arg)
252 {
253   if (set_mailbox_ownership_list (arg))
254     exit (po->po_exit_error);
255 }
256 
257 static void
cli_onerror(struct mu_parseopt * po,struct mu_option * opt,char const * arg)258 cli_onerror (struct mu_parseopt *po, struct mu_option *opt, char const *arg)
259 {
260   if (set_onerror_actions (arg))
261     exit (po->po_exit_error);
262 }
263 
264 static struct mu_option movemail_options[] = {
265   { "preserve", 'p', NULL, MU_OPTION_DEFAULT,
266     N_("preserve the source mailbox"),
267     mu_c_bool, &preserve_mail },
268   { "keep-messages", 0, NULL, MU_OPTION_ALIAS },
269 
270   { "reverse",  'r', NULL, MU_OPTION_DEFAULT,
271     N_("reverse the sorting order"),
272     mu_c_bool, &reverse_order },
273 
274   { "emacs",      0, NULL, MU_OPTION_DEFAULT,
275     N_("output information used by Emacs rmail interface"),
276     mu_c_bool, &emacs_mode },
277 
278   { "uidl",     'u', NULL, MU_OPTION_DEFAULT,
279     N_("use UIDLs to avoid downloading the same message twice"),
280     mu_c_bool, &uidl_option },
281 
282   { "verbose",  'v', NULL, MU_OPTION_DEFAULT,
283     N_("increase verbosity level"),
284     mu_c_incr, &verbose_option },
285 
286   { "owner",    'P', N_("MODELIST"), MU_OPTION_DEFAULT,
287     N_("control mailbox ownership"),
288     mu_c_string, cli_mailbox_ownership },
289 
290   { "ignore-errors", 0, NULL, MU_OPTION_DEFAULT,
291     N_("try to continue after errors"),
292     mu_c_bool, &ignore_errors },
293 
294   { "onerror",       0, N_("KW[,KW...]"), MU_OPTION_DEFAULT,
295     N_("what to do on errors"),
296     mu_c_string, NULL, cli_onerror },
297 
298   { "program-id",    0, N_("FMT"), MU_OPTION_DEFAULT,
299     N_("set program identifier for diagnostics (default: program name)"),
300     mu_c_string, &program_id_option },
301 
302   { "max-messages",  0, N_("NUMBER"), MU_OPTION_DEFAULT,
303     N_("process at most NUMBER messages"),
304     mu_c_size, &max_messages_option },
305 
306   { "notify",        0, NULL,   MU_OPTION_DEFAULT,
307     N_("enable biff notification"),
308     mu_c_bool, &notify },
309 
310   { "progress-meter", 'm', NULL,   MU_OPTION_DEFAULT,
311     N_("enable progress meter"),
312     mu_c_bool, &progress_meter_option },
313 
314   MU_OPTION_END
315 }, *options[] = { movemail_options, NULL };
316 
317 static int
cb_mailbox_ownership(void * data,mu_config_value_t * val)318 cb_mailbox_ownership (void *data, mu_config_value_t *val)
319 {
320   int i;
321 
322   if (val->type == MU_CFG_STRING)
323     set_mailbox_ownership_list (val->v.string);
324 
325   if (mu_cfg_assert_value_type (val, MU_CFG_LIST))
326     return 1;
327 
328   for (i = 0; i < val->v.arg.c; i++)
329     {
330       if (mu_cfg_assert_value_type (&val->v.arg.v[i], MU_CFG_STRING))
331 	return 1;
332       if (set_mailbox_ownership (val->v.arg.v[i].v.string))
333 	return 1;
334     }
335   return 0;
336 }
337 
338 static int
cb_onerror(void * data,mu_config_value_t * val)339 cb_onerror (void *data, mu_config_value_t *val)
340 {
341   switch (val->type)
342     {
343     case MU_CFG_LIST:
344       mu_list_foreach (val->v.list, set_onerror_action, NULL);
345       break;
346 
347     case MU_CFG_STRING:
348       set_onerror_actions (val->v.string);
349       break;
350 
351     default:
352       mu_error ("%s", _("too many arguments"));
353     }
354   return 0;
355 }
356 
357 struct mu_cfg_param movemail_cfg_param[] = {
358   { "preserve", mu_c_bool, &preserve_mail, 0, NULL,
359     N_("Do not remove messages from the source mailbox.") },
360   { "reverse",  mu_c_bool, &reverse_order, 0, NULL,
361     N_("Reverse message sorting order.") },
362   { "emacs", mu_c_bool, &emacs_mode, 0, NULL,
363     N_("Output information used by Emacs rmail interface.") },
364   { "uidl", mu_c_bool, &uidl_option, 0, NULL,
365     N_("Use UIDLs to avoid downloading the same message twice.") },
366   { "verbose", mu_c_int, &verbose_option, 0, NULL,
367     N_("Set verbosity level.") },
368   { "program-id", mu_c_string, &program_id_option, 0, NULL,
369     N_("Set program identifier string (default: program name)") },
370   { "mailbox-ownership", mu_cfg_callback, NULL, 0,
371     cb_mailbox_ownership,
372     N_("Define a list of methods for setting mailbox ownership. Valid "
373        "methods are:\n"
374        " copy-id          get owner UID and GID from the source mailbox\n"
375        " copy-name        get owner name from the source mailbox URL\n"
376        " set-id=UID[:GID] set supplied UID and GID\n"
377        " set-name=USER    make destination mailbox owned by USER"),
378     N_("methods: list") },
379   { "max-messages", mu_c_size, &max_messages_option, 0, NULL,
380     N_("Copy at most <count> messages."),
381     N_("count") },
382   { "ignore-errors", mu_c_bool, &ignore_errors, 0, NULL,
383     N_("Continue after an error.") },
384   { "onerror", mu_cfg_callback, NULL, 0, cb_onerror,
385     N_("What to do after an error. Argument is a list of:\n"
386        " abort  -  terminate the program (the default)\n"
387        " skip   -  skip to the next message\n"
388        " delete -  delete this one and to the next message\n"
389        " count  -  count this message as processed\n"
390        "Each keyword can be prefixed with \"no\" to reverse its meaning."),
391     N_("arg: list") },
392   { NULL }
393 };
394 
395 struct mu_cli_setup cli = {
396   options,
397   movemail_cfg_param,
398   N_("GNU movemail -- move messages across mailboxes."),
399   N_("inbox-url destfile [POP-password]")
400 };
401 
402 static char *movemail_capa[] = {
403   "debug",
404   "locking",
405   "mailbox",
406   "auth",
407   NULL
408 };
409 
410 void
die(mu_mailbox_t mbox,const char * msg,int status)411 die (mu_mailbox_t mbox, const char *msg, int status)
412 {
413   mu_url_t url = NULL;
414 
415   mu_mailbox_get_url (mbox, &url);
416   if (emacs_mode)
417     mu_error (_("%s:mailbox `%s': %s: %s"),
418 	      mu_errname (status),
419 	      mu_url_to_string (url),
420 	      msg,
421 	      mu_strerror (status));
422   else
423     mu_error (_("mailbox `%s': %s: %s"),
424 	      mu_url_to_string (url), msg, mu_strerror (status));
425   exit (1);
426 }
427 
428 void
lock_mailbox(mu_mailbox_t mbox)429 lock_mailbox (mu_mailbox_t mbox)
430 {
431   mu_locker_t lock;
432   int status;
433 
434   status = mu_mailbox_get_locker (mbox, &lock);
435   if (status)
436     die (mbox, _("cannot retrieve locker"), status);
437 
438   if (!lock)
439     /* Remote mailboxes have no lockers */
440     return;
441 
442   status = mu_locker_lock (lock);
443 
444   if (status)
445     die (mbox, _("cannot lock"), status);
446 }
447 
448 
449 void
attach_passwd_ticket(mu_mailbox_t mbx,char * passwd)450 attach_passwd_ticket (mu_mailbox_t mbx, char *passwd)
451 {
452   mu_folder_t folder = NULL;
453   mu_authority_t auth = NULL;
454   mu_secret_t secret;
455   mu_ticket_t t;
456   int rc;
457 
458   rc = mu_secret_create (&secret, passwd, strlen (passwd));
459   if (rc)
460     {
461       mu_error ("mu_secret_create: %s", mu_strerror (rc));
462       exit (1);
463     }
464 
465   mu_ticket_create (&t, NULL);
466   mu_ticket_set_secret (t, secret);
467 
468   if ((rc = mu_mailbox_get_folder (mbx, &folder)))
469     die (mbx, _("mu_mailbox_get_folder failed"), rc);
470 
471   if ((rc = mu_folder_get_authority (folder, &auth)))
472     die (mbx, _("mu_folder_get_authority failed"), rc);
473 
474   if (auth && (rc = mu_authority_set_ticket (auth, t)))
475     die (mbx, _("mu_authority_set_ticket failed"), rc);
476 }
477 
478 
479 /* Create and open a mailbox associated with the given URL,
480    flags and (optionally) password */
481 void
open_mailbox(mu_mailbox_t * mbx,char * name,int flags,char * passwd)482 open_mailbox (mu_mailbox_t *mbx, char *name, int flags, char *passwd)
483 {
484   int status = mu_mailbox_create_default (mbx, name);
485 
486   if (status)
487     {
488       if (name)
489 	mu_error (_("could not create mailbox `%s': %s"),
490 		  name,
491 		  mu_strerror (status));
492       else
493 	mu_error (_("could not create default mailbox: %s"),
494 		  mu_strerror (status));
495       exit (1);
496     }
497 
498   if (passwd)
499     attach_passwd_ticket (*mbx, passwd);
500   status = mu_mailbox_open (*mbx, flags);
501   if (status)
502     die (*mbx, _("cannot open"), status);
503   lock_mailbox (*mbx);
504 }
505 
506 int
move_message(mu_mailbox_t dst,mu_message_t msg,size_t msgno)507 move_message (mu_mailbox_t dst, mu_message_t msg, size_t msgno)
508 {
509   int rc;
510 
511   if ((rc = mu_mailbox_append_message (dst, msg)) != 0)
512     {
513       mu_error (_("cannot append message %lu: %s"),
514 		(unsigned long) msgno, mu_strerror (rc));
515       if (!(onerror_flags & ONERROR_DELETE))
516 	return rc;
517     }
518   if (!preserve_mail)
519     {
520       mu_attribute_t attr;
521       mu_message_get_attribute (msg, &attr);
522       mu_attribute_set_deleted (attr);
523     }
524   return rc;
525 }
526 
527 int
movemail(mu_mailbox_t dst,mu_message_t msg,size_t msgno)528 movemail (mu_mailbox_t dst, mu_message_t msg, size_t msgno)
529 {
530   int rc = move_message (dst, msg, msgno);
531   if (rc == 0)
532     ++msg_count;
533   else
534     {
535       app_err_count++;
536       if (onerror_flags)
537 	{
538 	  if (onerror_flags & ONERROR_COUNT)
539 	    ++msg_count;
540 	}
541       else
542 	return 1;
543     }
544   return max_messages_option && msg_count >= max_messages_option;
545 }
546 
547 /* Open source mailbox using compatibility syntax. Source_name is
548    of the form:
549 
550      po:USERNAME[:POP-SERVER]
551 
552    if POP-SERVER part is omitted, the MAILHOST environment variable
553    will be consulted. */
554 void
compatibility_mode(mu_mailbox_t * mbx,char * source_name,char * password,int flags)555 compatibility_mode (mu_mailbox_t *mbx, char *source_name, char *password,
556 		    int flags)
557 {
558   char *tmp;
559   char *user_name = strtok (source_name+3, ":");
560   char *host = strtok (NULL, ":");
561   if (!host)
562     host = getenv ("MAILHOST");
563   if (!host)
564     {
565       mu_error (_("hostname of the POP3 server is unknown"));
566       exit (1);
567     }
568   mu_asprintf (&tmp, "pop://%s@%s", user_name, host);
569   open_mailbox (mbx, tmp, flags, password);
570   free (tmp);
571 }
572 
573 static mu_mailbox_t source, dest;
574 
575 static void
close_mailboxes(void)576 close_mailboxes (void)
577 {
578   mu_mailbox_close (dest);
579   mu_mailbox_close (source);
580 }
581 
582 static int
get_mbox_owner_id(mu_mailbox_t mbox,mu_url_t url,struct user_id * id)583 get_mbox_owner_id (mu_mailbox_t mbox, mu_url_t url, struct user_id *id)
584 {
585   const char *s;
586   int rc = mu_url_sget_scheme  (url, &s);
587   if (rc)
588     die (mbox, _("cannot get scheme"), rc);
589   if ((strcmp (s, "/") == 0
590        || strcmp (s, "mbox") == 0
591        || strcmp (s, "mh") == 0
592        || strcmp (s, "maildir") == 0))
593     {
594       struct stat st;
595 
596       rc = mu_url_sget_path  (url, &s);
597       if (rc)
598 	die (mbox, _("cannot get path"), rc);
599       if (stat (s, &st))
600 	{
601 	  mu_diag_funcall (MU_DIAG_ERROR, "stat", s, errno);
602 	  exit (1);
603 	}
604       id->uid = st.st_uid;
605       id->gid = st.st_gid;
606       return 0;
607     }
608   else if (verbose_option)
609     mu_diag_output (MU_DIAG_WARNING,
610 		    _("ignoring copy-name: not a local mailbox"));
611   return 1;
612 }
613 
614 static int
get_user_id(const char * name,struct user_id * id)615 get_user_id (const char *name, struct user_id *id)
616 {
617   struct mu_auth_data *auth = mu_get_auth_by_name (name);
618 
619   if (!auth)
620     {
621       if (verbose_option)
622 	mu_diag_output (MU_DIAG_WARNING, _("no such user: %s"), name);
623       return 1;
624     }
625 
626   id->uid = auth->uid;
627   id->gid = auth->gid;
628   mu_auth_data_free (auth);
629   return 0;
630 }
631 
632 static int
get_mbox_owner_name(mu_mailbox_t mbox,mu_url_t url,struct user_id * id)633 get_mbox_owner_name (mu_mailbox_t mbox, mu_url_t url, struct user_id *id)
634 {
635   const char *s;
636   int rc = mu_url_sget_user (url, &s);
637   if (rc)
638     /* FIXME */
639     die (mbox, _("cannot get mailbox owner name"), rc);
640 
641   return get_user_id (s, id);
642 }
643 
644 static int
guess_mbox_owner(mu_mailbox_t mbox,struct user_id * id)645 guess_mbox_owner (mu_mailbox_t mbox, struct user_id *id)
646 {
647   mu_url_t url = NULL;
648   int rc;
649   struct set_ownership_method *meth;
650 
651   rc = mu_mailbox_get_url (mbox, &url);
652   if (rc)
653     {
654       mu_diag_funcall (MU_DIAG_ERROR, "mu_mailbox_get_url", NULL, rc);
655       exit (1);
656     }
657 
658   rc = 1;
659   for (meth = so_methods; rc == 1 && meth < so_methods + so_method_num; meth++)
660     {
661       switch (meth->mode)
662 	{
663 	case copy_owner_id:
664 	  rc = get_mbox_owner_id (mbox, url, id);
665 	  break;
666 
667 	case copy_owner_name:
668 	  rc = get_mbox_owner_name (mbox, url, id);
669 	  break;
670 
671 	case set_owner_id:
672 	  id->uid = meth->owner.id.uid;
673 	  rc = 0;
674 	  if (meth->owner.id.gid == (gid_t)-1)
675 	    {
676 	      struct passwd *pw = getpwuid (id->uid);
677 	      if (pw)
678 		id->gid = pw->pw_gid;
679 	      else
680 		{
681 		  if (verbose_option)
682 		    mu_diag_output (MU_DIAG_WARNING,
683 				    _("no user with uid %lu found"),
684 				    (unsigned long) id->uid);
685 		  rc = 1;
686 		}
687 	    }
688 	  else
689 	    id->gid = meth->owner.id.gid;
690 	  break;
691 
692 	case set_owner_name:
693 	  rc = get_user_id (meth->owner.name, id);
694 	  break;
695 	}
696     }
697 
698   return rc;
699 }
700 
701 static void
switch_owner(mu_mailbox_t mbox)702 switch_owner (mu_mailbox_t mbox)
703 {
704   struct user_id user_id;
705 
706   if (so_method_num == 0)
707     return;
708 
709   if (getuid ())
710     {
711       if (verbose_option)
712 	mu_diag_output (MU_DIAG_WARNING,
713 			_("ignoring mailbox-ownership statement"));
714       return;
715     }
716 
717   if (guess_mbox_owner (mbox, &user_id) == 0)
718     {
719       if (mu_switch_to_privs (user_id.uid, user_id.gid, NULL))
720 	exit (1);
721     }
722   else
723     {
724       mu_error (_("no suitable method for setting mailbox ownership"));
725       exit (1);
726     }
727 }
728 
729 static int
_compare_uidls(const void * item,const void * value)730 _compare_uidls (const void *item, const void *value)
731 {
732   const struct mu_uidl *a = item;
733   const struct mu_uidl *b = value;
734 
735   return strcmp (a->uidl, b->uidl);
736 }
737 
738 struct movemail_getvar_closure
739 {
740   const char *source_name;
741   const char *dest_name;
742   mu_url_t source_url;
743   mu_url_t dest_url;
744 };
745 
746 #define SEQ(s, n, l) \
747   (((l) == (sizeof(s) - 1)) && memcmp (s, n, l) == 0)
748 
749 static int
get_url_part(mu_url_t url,const char * name,size_t nlen,char ** ret)750 get_url_part (mu_url_t url, const char *name, size_t nlen, char **ret)
751 {
752   int rc;
753 
754   if (!url)
755     return MU_WRDSE_UNDEF;
756   if (SEQ ("user", name, nlen))
757     rc = mu_url_aget_user (url, ret);
758   else if (SEQ ("host", name, nlen))
759     rc = mu_url_aget_host (url, ret);
760   else if (SEQ ("port", name, nlen))
761     rc = mu_url_aget_portstr (url, ret);
762   else if (SEQ ("path", name, nlen))
763     rc = mu_url_aget_path (url, ret);
764   else
765     return MU_WRDSE_UNDEF;
766 
767   switch (rc)
768     {
769     case 0:
770       break;
771 
772     case MU_ERR_NOENT:
773       return MU_WRDSE_UNDEF;
774 
775     default:
776       if (mu_asprintf (ret, "%s", mu_strerror (rc)))
777 	return MU_WRDSE_NOSPACE;
778       return MU_WRDSE_USERERR;
779     }
780 
781   return MU_WRDSE_OK;
782 }
783 
784 static int
movemail_getvar(char ** ret,const char * name,size_t nlen,void * data)785 movemail_getvar (char **ret, const char *name, size_t nlen, void *data)
786 {
787   struct movemail_getvar_closure *pc = data;
788   const char *s;
789 
790   if (nlen > 7 && memcmp ("source_", name, 7) == 0)
791     return get_url_part (pc->source_url, name + 7, nlen - 7, ret);
792 
793   if (nlen > 5 && memcmp ("dest_", name, 5) == 0)
794     return get_url_part (pc->dest_url, name + 5, nlen - 5, ret);
795 
796   if (SEQ ("progname", name, nlen))
797     s = mu_program_name;
798   else if (SEQ ("source", name, nlen))
799     s = pc->source_name;
800   else if (SEQ ("dest", name, nlen))
801     s = pc->dest_name;
802   else
803     return MU_WRDSE_UNDEF;
804 
805   *ret = strdup (s);
806   if (!*ret)
807     return MU_WRDSE_NOSPACE;
808   return MU_WRDSE_OK;
809 }
810 
811 static void
set_program_id(const char * source_name,const char * dest_name)812 set_program_id (const char *source_name, const char *dest_name)
813 {
814   int rc;
815   struct mu_wordsplit ws;
816   struct movemail_getvar_closure clos;
817 
818   clos.source_name = source_name;
819   clos.dest_name = dest_name;
820   rc = mu_mailbox_get_url (source, &clos.source_url);
821   if (rc)
822     mu_diag_output (MU_DIAG_INFO,
823 		    _("cannot obtain source mailbox URL: %s"),
824 		    mu_strerror (rc));
825   rc = mu_mailbox_get_url (dest, &clos.dest_url);
826   if (rc)
827     mu_diag_output (MU_DIAG_INFO,
828 		    _("cannot obtain destination mailbox URL: %s"),
829 		    mu_strerror (rc));
830 
831   ws.ws_getvar = movemail_getvar;
832   ws.ws_closure = &clos;
833   if (mu_wordsplit (program_id_option, &ws,
834 		    MU_WRDSF_NOSPLIT | MU_WRDSF_NOCMD |
835 		    MU_WRDSF_GETVAR | MU_WRDSF_CLOSURE))
836     {
837       mu_error (_("cannot expand line `%s': %s"), program_id_option,
838 		mu_wordsplit_strerror (&ws));
839       return;
840     }
841 
842   /* FIXME: Don't use mu_set_program_name here, because it
843      plays wise with its argument. We need a mu_set_diag_prefix
844      function. */
845   mu_program_name = ws.ws_wordv[0];
846   ws.ws_wordc = 0;
847   mu_wordsplit_free (&ws);
848   mu_stdstream_strerr_setup (MU_STRERR_STDERR);
849 }
850 
851 static int
screen_width(void)852 screen_width (void)
853 {
854   struct winsize ws;
855   ws.ws_col = 0;
856   if (ioctl(1, TIOCGWINSZ, (char *) &ws) < 0)
857     {
858       char *p = getenv ("COLUMNS");
859       if (p)
860 	ws.ws_col = atol (p);
861     }
862   if (ws.ws_col == 0)
863     return 80;
864   return ws.ws_col;
865 }
866 
867 static void
progress_format(size_t pos,size_t count)868 progress_format (size_t pos, size_t count)
869 {
870   int n;
871 
872   fputc ('\r', stdout);
873   n = printf ("message %zu/%zu", pos, count);
874   n = screen_width () - n;
875   while (n--)
876     fputc (' ', stdout);
877   fflush (stdout);
878 }
879 
880 void
progress_start(mu_iterator_t itr)881 progress_start (mu_iterator_t itr)
882 {
883   size_t count;
884 
885   if (!progress_meter_option)
886     return;
887 
888   if (mu_iterator_ctl (itr, mu_itrctl_count, &count))
889     {
890       progress_meter_option = 0;
891       return;
892     }
893   progress_format (0, count);
894 }
895 
896 void
progress_mark(mu_iterator_t itr)897 progress_mark (mu_iterator_t itr)
898 {
899   size_t count, pos;
900 
901   if (!progress_meter_option)
902     return;
903 
904   if (mu_iterator_ctl (itr, mu_itrctl_count, &count)
905       || mu_iterator_ctl (itr, mu_itrctl_tell, &pos))
906     {
907       progress_meter_option = 0;
908       return;
909     }
910   if (reverse_order)
911     pos = count - pos + 1;
912   progress_format (pos, count);
913 }
914 
915 void
progress_stop(void)916 progress_stop (void)
917 {
918   if (progress_meter_option)
919     fputc ('\n', stdout);
920 }
921 
922 int
main(int argc,char ** argv)923 main (int argc, char **argv)
924 {
925   size_t total;
926   int rc = 0;
927   char *source_name, *dest_name;
928   int flags;
929   mu_list_t src_uidl_list = NULL;
930   mu_iterator_t itr;
931 
932   /* Native Language Support */
933   MU_APP_INIT_NLS ();
934   MU_AUTH_REGISTER_ALL_MODULES ();
935 
936   /* Register the desired "mailbox" formats.  */
937   mu_register_all_formats ();
938   /* Register authentication modules */
939   mu_auth_register_module (&mu_auth_tls_module);
940 
941   /* argument parsing */
942   mu_cli (argc, argv, &cli, movemail_capa, NULL, &argc, &argv);
943 
944   if (argc < 2 || argc > 3)
945     {
946       mu_error (_("wrong number of arguments"));
947       return 1;
948     }
949 
950   if (ignore_errors)
951     onerror_flags |= ONERROR_SKIP|ONERROR_COUNT;
952 
953   if (!isatty (1))
954     progress_meter_option = 0;
955 
956   if (emacs_mode)
957     {
958       /* Undo the effect of configuration options that may affect
959 	 interaction with Emacs. */
960       mu_registrar_set_default_record (mu_mbox_record);
961       mu_stdstream_strerr_setup (MU_STRERR_STDERR);
962     }
963 
964   atexit (close_mailboxes);
965 
966   source_name = argv[0];
967   dest_name = argv[1];
968 
969   flags = preserve_mail ? MU_STREAM_READ : MU_STREAM_RDWR;
970 
971   if (strncmp (source_name, "po:", 3) == 0)
972     compatibility_mode (&source, source_name, argv[2], flags);
973   else
974     open_mailbox (&source, source_name, flags, argv[2]);
975 
976   switch_owner (source);
977 
978   open_mailbox (&dest, dest_name,
979 		MU_STREAM_APPEND | MU_STREAM_READ | MU_STREAM_CREAT, NULL);
980 
981   if (program_id_option)
982     set_program_id (source_name, dest_name);
983 
984   if (notify)
985     {
986       rc = mu_mailbox_set_notify (dest, NULL);
987       if (rc)
988 	mu_error (_("failed to set up notification: %s"),
989 		  mu_strerror (rc));
990     }
991 
992   rc = mu_mailbox_messages_count (source, &total);
993   if (rc)
994     {
995       mu_error(_("cannot count messages: %s"), mu_strerror (rc));
996       exit (1);
997     }
998 
999   if (verbose_option)
1000     {
1001       mu_diag_output (MU_DIAG_INFO,
1002 		      _("number of messages in source mailbox: %lu"),
1003 		      (unsigned long) total);
1004       if (max_messages_option)
1005 	mu_diag_output (MU_DIAG_INFO,
1006 			reverse_order ?
1007 			ngettext ("will process last %lu message",
1008 				  "will process last %lu messages",
1009 				  max_messages_option) :
1010 			ngettext ("will process first %lu message",
1011 				  "will process first %lu messages",
1012 				  max_messages_option),
1013 			(unsigned long) max_messages_option);
1014     }
1015 
1016   if (uidl_option)
1017     {
1018       mu_list_t dst_uidl_list = NULL;
1019 
1020       rc = mu_mailbox_get_uidls (source, &src_uidl_list);
1021       if (rc)
1022 	die (source, _("cannot get UIDLs"), rc);
1023       rc = mu_mailbox_get_uidls (dest, &dst_uidl_list);
1024       if (rc)
1025 	die (dest, _("cannot get UIDLs"), rc);
1026 
1027       mu_list_set_comparator (dst_uidl_list, _compare_uidls);
1028 
1029       mu_list_get_iterator (src_uidl_list, &itr);
1030       for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
1031 	   mu_iterator_next (itr))
1032 	{
1033 	  struct mu_uidl *uidl;
1034 
1035 	  mu_iterator_current (itr, (void **)&uidl);
1036 	  if (mu_list_locate (dst_uidl_list, uidl, NULL) == 0)
1037 	    mu_iterator_ctl (itr, mu_itrctl_delete, NULL);
1038 	}
1039       mu_list_destroy (&dst_uidl_list);
1040       mu_list_set_comparator (src_uidl_list, NULL);
1041 
1042       rc = mu_iterator_ctl (itr, mu_itrctl_set_direction, &reverse_order);
1043       if (rc)
1044 	{
1045 	  mu_error (_("cannot set iteration direction: %s"), mu_strerror (rc));
1046 	  exit (1);
1047 	}
1048 
1049       progress_start (itr);
1050       for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
1051 	   mu_iterator_next (itr))
1052 	{
1053 	  struct mu_uidl *uidl;
1054 	  mu_message_t msg;
1055 	  mu_header_t hdr;
1056 
1057 	  mu_iterator_current (itr, (void **)&uidl);
1058 
1059 	  if ((rc = mu_mailbox_get_message (source, uidl->msgno, &msg)) != 0)
1060 	    {
1061 	      mu_error (_("cannot read message %lu: %s"),
1062 			(unsigned long) uidl->msgno, mu_strerror (rc));
1063 	      get_err_count++;
1064 	      continue;
1065 	    }
1066 
1067 	  /* Check if the downloaded message has X-UIDL header. If not,
1068 	     add one. This check should disappear one mailutils implements
1069 	     alternative storage for mailbox meta-data. */
1070 	  if ((rc = mu_message_get_header (msg, &hdr)))
1071 	    {
1072 	      mu_error (_("%lu: cannot get header: %s"),
1073 			(unsigned long) uidl->msgno, mu_strerror (rc));
1074 	    }
1075 	  else
1076 	    {
1077 	      char const *suidl = NULL;
1078 
1079 	      if ((rc = mu_header_sget_value (hdr, MU_HEADER_X_UIDL, &suidl)))
1080 		{
1081 		  if (rc != MU_ERR_NOENT)
1082 		    mu_error (_("%lu: cannot get %s: %s"),
1083 			      (unsigned long) uidl->msgno, MU_HEADER_X_UIDL,
1084 			      mu_strerror (rc));
1085 		}
1086 	      else if (strcmp (suidl, uidl->uidl))
1087 		{
1088 		  mu_error (_("%lu: stored and reported UIDL differ; fixing"),
1089 			    (unsigned long) uidl->msgno);
1090 		  suidl = NULL;
1091 		}
1092 
1093 	      if ((rc = mu_header_set_value (hdr, MU_HEADER_X_UIDL,
1094 					     uidl->uidl, 1)))
1095 		{
1096 		  mu_error (_("%lu: cannot set header: %s"),
1097 			    (unsigned long) uidl->msgno, mu_strerror (rc));
1098 		}
1099 	    }
1100 	  progress_mark (itr);
1101 	  if (movemail (dest, msg, uidl->msgno))
1102 	    break;
1103 	}
1104     }
1105   else
1106     {
1107       rc = mu_mailbox_get_iterator (source, &itr);
1108       if (rc)
1109 	{
1110 	  mu_error (_("cannot obtain mailbox iterator: %s"), mu_strerror (rc));
1111 	  return 1;
1112 	}
1113 
1114       rc = mu_iterator_ctl (itr, mu_itrctl_set_direction, &reverse_order);
1115       if (rc)
1116 	{
1117 	  mu_error (_("cannot set iteration direction: %s"),
1118 		    mu_strerror (rc));
1119 	  return 1;
1120 	}
1121 
1122       progress_start (itr);
1123       for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
1124 	   mu_iterator_next (itr))
1125 	{
1126 	  mu_message_t msg;
1127 	  size_t msgno;
1128 
1129 	  rc = mu_iterator_ctl (itr, mu_itrctl_tell, &msgno);
1130 	  if (rc)
1131 	    {
1132 	      mu_error (_("cannot get iterator position: %s"),
1133 			mu_strerror (rc));
1134 	      return 1;
1135 	    }
1136 
1137 	  rc = mu_iterator_current (itr, (void **)&msg);
1138 	  if (rc)
1139 	    {
1140 	      mu_error (_("cannot read message %lu: %s"),
1141 			(unsigned long) msgno, mu_strerror (rc));
1142 	      get_err_count++;
1143 	      continue;
1144 	    }
1145 
1146 	  if (movemail (dest, msg, msgno))
1147 	    break;
1148 	  progress_mark (itr);
1149 	}
1150     }
1151   progress_stop ();
1152   mu_iterator_destroy (&itr);
1153 
1154   if (verbose_option)
1155     {
1156       mu_diag_output (MU_DIAG_INFO,
1157 		      _("number of processed messages: %lu"),
1158 		      (unsigned long) msg_count);
1159       mu_diag_output (MU_DIAG_INFO,
1160 		      _("number of errors: %lu / %lu"),
1161 		      (unsigned long) get_err_count,
1162 		      (unsigned long) app_err_count);
1163     }
1164 
1165   if (app_err_count && !(onerror_flags & (ONERROR_DELETE|ONERROR_COUNT)))
1166     preserve_mail = 1;
1167   if (onerror_flags & ONERROR_COUNT)
1168     app_err_count = 0;
1169 
1170   mu_mailbox_sync (dest);
1171   rc = mu_mailbox_close (dest);
1172   mu_mailbox_destroy (&dest);
1173   if (rc)
1174     mu_error (_("cannot close destination mailbox: %s"), mu_strerror (rc));
1175   else if (!preserve_mail)
1176     mu_mailbox_expunge (source);
1177 
1178   mu_mailbox_close (source);
1179   mu_mailbox_destroy (&source);
1180 
1181   return !(rc == 0 && (app_err_count + get_err_count) == 0);
1182 }
1183