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, ¬ify },
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