2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2008 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
16 #include "radio.h" /* OE_PASSWD */
17 #include "../pith/util.h" /* IS_REMOTE() */
18 #include "../pith/remote.h" /* REMOTE_ABOOK_SUBTYPE... */
21 typedef enum {Pinerc
, Abook
, Sig
, Smime
, NotSet
} RemoteType
;
24 int parse_args(int, char **, int *, int *, char **, char **, RemoteType
*);
25 RemoteType
check_for_header_msg(MAILSTREAM
*);
26 char *ptype(RemoteType
);
27 char *spechdr(RemoteType
);
28 int add_initial_msg(MAILSTREAM
*, char *, char *);
29 int append_data(MAILSTREAM
*, char *, char *, FILE *);
30 void trim_data(MAILSTREAM
*, int);
31 void write_fake_headers(RFC822BUFFER
*, char *, char *, char *);
33 int opt_enter(char *, int, char *, int *);
37 char *ustr
= "usage: %s [-s trimSize] [-f] -t Type -l Local_file -r Remote_folder\n";
40 /* look for my_timer_period in pico directory for an explanation */
41 int my_timer_period
= ((IDLE_TIMEOUT
+ 1)*1000);
58 * rpload [-s trimSize] [-f] -t Type -l Local_file -r Remote_folder
60 * Type is one of abook
63 * sig (this is mostly obsolete, literal sigs
64 * should be used instead)
65 * -f means force the folder to be written even if it
66 * doesn't look like it is of the right format
68 * Note: We're not worrying about memory leaks.
75 MAILSTREAM
*stream
= NULL
;
77 int delete_existing
= 0, usage
= 0;
78 int force
= 0, trimsize
= 0;
79 char *local
= NULL
, *remote
= NULL
, *special_hdr
= NULL
;
80 RemoteType rt
, type
= NotSet
;
82 #include "../c-client/linkage.c"
84 if(parse_args(argc
, argv
, &force
, &trimsize
, &local
, &remote
, &type
)){
85 fprintf(stderr
, ustr
, argv
[0]);
89 if(!local
|| !*local
){
90 fprintf(stderr
, "No local file specified\n");
94 if(!remote
|| !*remote
){
95 fprintf(stderr
, "No remote folder specified\n");
100 fprintf(stderr
, "Type must be set to one of:\n");
101 for(rt
= Pinerc
; rt
!= NotSet
; rt
++)
102 fprintf(stderr
, " %s\n", ptype(rt
));
108 fprintf(stderr
, ustr
, argv
[0]);
112 if(!IS_REMOTE(remote
)){
114 "Remote folder name \"%s\" %s\n", remote
,
115 (*remote
!= '{') ? "must begin with \"{\"" : "not valid");
119 if(IS_REMOTE(local
)){
120 fprintf(stderr
, "Argument to -l (%s) must be a local filename", local
);
124 if(access(local
, ACCESS_EXISTS
) != 0){
125 fprintf(stderr
, "Local file \"%s\" does not exist\n", local
);
129 if(access(local
, READ_ACCESS
) != 0){
131 "Can't read local file \"%s\": %s\n",
132 local
, err_desc(errno
));
137 * Try opening the local file.
139 if((fp
= fopen(local
, "r")) == NULL
){
140 fprintf(stderr
, "Can't open \"%s\": %s\n", local
, err_desc(errno
));
145 * Try opening the remote folder. If it doesn't exist, create it.
148 /* failure would be normal here, so don't show it */
150 stream
= mail_open(NULL
, remote
, 0L);
151 if(!stream
|| stream
->halfopen
){
152 if(stream
&& stream
->halfopen
){
154 if(!mail_create(stream
, remote
))
157 stream
= mail_open(stream
, remote
, 0L);
158 if(!stream
|| stream
->halfopen
)
162 fprintf(stderr
, "Trouble opening remote folder \"%s\"\n", remote
);
170 fprintf(stderr
, "Remote folder \"%s\" is not writable\n", remote
);
174 if(stream
->nmsgs
> 0){
176 * There is a first message already. Check to see if it is one of
177 * our special header messages.
179 rt
= check_for_header_msg(stream
);
184 fprintf(stderr
, "Folder \"%s\"\ndoes not appear to be an Alpine remote \"%s\" folder.\nUse -f to force.\n", remote
, ptype(type
));
185 fprintf(stderr
, "-f will cause %ld messages to be deleted\n",
194 fprintf(stderr
, "Folder \"%s\" is type \"%s\"\nUse -f to force switch.\n", remote
, ptype(rt
));
195 fprintf(stderr
, "-f will cause %ld messages to be deleted\n",
206 snprintf(sequence
, sizeof(sequence
), "1:%ld", stream
->nmsgs
);
207 mail_flag(stream
, sequence
, "\\DELETED", ST_SET
);
208 mail_expunge(stream
);
212 special_hdr
= spechdr(type
);
215 * Add the explanatory header message if needed.
217 if(stream
->nmsgs
== 0){
218 if(add_initial_msg(stream
, remote
, special_hdr
) != 0){
225 * Add the actual data in a message.
227 if(append_data(stream
, remote
, special_hdr
, fp
) != 0){
233 * Trim the size of the remote folder.
236 trim_data(stream
, trimsize
);
244 check_for_header_msg(stream
)
251 char *pinerc
, *abook
, *sig
, *smime
;
253 pinerc
= spechdr(Pinerc
);
254 abook
= spechdr(Abook
);
256 smime
= spechdr(Smime
);
258 len
= MAX(MAX(strlen(pinerc
), strlen(abook
)), MAX(strlen(sig
), strlen(smime
)));
260 sl
= mail_newstringlist();
261 sl
->text
.data
= (unsigned char *)fs_get((len
+1) * sizeof(unsigned char));
263 strncpy((char *)sl
->text
.data
, try, len
);
264 sl
->text
.data
[len
] = '\0';
265 sl
->text
.size
= strlen((char *) sl
->text
.data
);
267 if(stream
&& (h
=mail_fetch_header(stream
,1L,NULL
,sl
,NULL
,FT_PEEK
))){
269 if(strlen(h
) >= sl
->text
.size
&& !struncmp(h
, try, sl
->text
.size
))
275 strncpy((char *)sl
->text
.data
, try, len
);
276 sl
->text
.data
[len
] = '\0';
277 sl
->text
.size
= strlen((char *) sl
->text
.data
);
278 if(stream
&& (h
=mail_fetch_header(stream
,1L,NULL
,sl
,NULL
,FT_PEEK
))){
280 if(strlen(h
) >= sl
->text
.size
&& !struncmp(h
, try, sl
->text
.size
))
287 strncpy((char *)sl
->text
.data
, try, len
);
288 sl
->text
.data
[len
] = '\0';
289 sl
->text
.size
= strlen((char *) sl
->text
.data
);
290 if(stream
&& (h
=mail_fetch_header(stream
,1L,NULL
,sl
,NULL
,FT_PEEK
))){
292 if(strlen(h
) >= sl
->text
.size
&& !struncmp(h
, try, sl
->text
.size
))
298 mail_free_stringlist(&sl
);
301 fs_give((void **)&pinerc
);
303 fs_give((void **)&abook
);
305 fs_give((void **)&sig
);
307 fs_give((void **)&smime
);
321 ret
= cpystr("pinerc");
324 ret
= cpystr("abook");
330 ret
= cpystr("smime");
348 ret
= cpystr(REMOTE_PINERC_SUBTYPE
);
351 ret
= cpystr(REMOTE_ABOOK_SUBTYPE
);
354 ret
= cpystr(REMOTE_SIG_SUBTYPE
);
357 ret
= cpystr(REMOTE_SMIME_SUBTYPE
);
368 parse_args(argc
, argv
, force
, trimsize
, local
, remote
, type
)
371 int *force
, *trimsize
;
372 char **local
, **remote
;
385 /* while more arguments with leading - */
386 Loop
: while(--ac
> 0 && **++av
== '-'){
387 /* while more chars in this argument */
397 case 't': case 'l': /* string args */
399 case 's': /* integer args */
405 fprintf(stderr
, "missing argument for flag \"%c\"\n", c
);
422 for(rt
= Pinerc
; rt
!= NotSet
; rt
++){
423 if(!strucmp(str
, ptype(rt
)))
430 if(!isdigit((unsigned char)str
[0])){
432 "non-numeric argument for flag \"%c\"\n", c
);
437 *trimsize
= atoi(str
);
439 fprintf(stderr
, "trimsize of %d is too small, have to leave at least one copy\n", *trimsize
);
442 else if(*trimsize
> 100){
444 "trimsize of %d is too large, 5 or 10 is sufficient\n",
455 fprintf(stderr
, "unknown flag \"%c\"\n", c
);
470 dummy_soutr(stream
, string
)
479 * Add an explanatory first message advising user that this is a
480 * special sort of folder.
483 add_initial_msg(stream
, mailbox
, special_hdr
)
492 rbuf
.f
= dummy_soutr
;
496 rbuf
.end
= buf
+sizeof(buf
)-1;
497 write_fake_headers(&rbuf
, "Header Message for Remote Data", "plain", special_hdr
);
500 buf
[sizeof(buf
)-1] = '\0';
502 if(!strucmp(special_hdr
, REMOTE_ABOOK_SUBTYPE
)){
503 strncat(buf
, "This folder contains a single Alpine addressbook.\015\012", sizeof(buf
)-strlen(buf
)-1);
504 strncat(buf
, "This message is just an explanatory message.\015\012", sizeof(buf
)-strlen(buf
)-1);
505 strncat(buf
, "The last message in the folder is the live addressbook data.\015\012", sizeof(buf
)-strlen(buf
)-1);
506 strncat(buf
, "The rest of the messages contain previous revisions of the addressbook data.\015\012", sizeof(buf
)-strlen(buf
)-1);
507 strncat(buf
, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf
)-strlen(buf
)-1);
508 strncat(buf
, "which come after it.\015\012", sizeof(buf
)-strlen(buf
)-1);
510 else if(!strucmp(special_hdr
, REMOTE_PINERC_SUBTYPE
)){
511 strncat(buf
, "This folder contains an Alpine config file.\015\012", sizeof(buf
)-strlen(buf
)-1);
512 strncat(buf
, "This message is just an explanatory message.\015\012", sizeof(buf
)-strlen(buf
)-1);
513 strncat(buf
, "The last message in the folder is the live config data.\015\012", sizeof(buf
)-strlen(buf
)-1);
514 strncat(buf
, "The rest of the messages contain previous revisions of the data.\015\012", sizeof(buf
)-strlen(buf
)-1);
515 strncat(buf
, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf
)-strlen(buf
)-1);
516 strncat(buf
, "which come after it.\015\012", sizeof(buf
)-strlen(buf
)-1);
518 else if(!strucmp(special_hdr
, REMOTE_SMIME_SUBTYPE
)){
519 strncat(buf
, "This folder contains Alpine S/MIME config information.\015\012", sizeof(buf
)-strlen(buf
)-1);
520 strncat(buf
, "This message is just an explanatory message.\015\012", sizeof(buf
)-strlen(buf
)-1);
521 strncat(buf
, "The last message in the folder is the live data.\015\012", sizeof(buf
)-strlen(buf
)-1);
522 strncat(buf
, "The rest of the messages contain previous revisions of the data.\015\012", sizeof(buf
)-strlen(buf
)-1);
523 strncat(buf
, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf
)-strlen(buf
)-1);
524 strncat(buf
, "which come after it.\015\012", sizeof(buf
)-strlen(buf
)-1);
527 strncat(buf
, "This folder contains remote Alpine data.\015\012", sizeof(buf
)-strlen(buf
)-1);
528 strncat(buf
, "This message is just an explanatory message.\015\012", sizeof(buf
)-strlen(buf
)-1);
529 strncat(buf
, "The last message in the folder is the live data.\015\012", sizeof(buf
)-strlen(buf
)-1);
530 strncat(buf
, "The rest of the messages contain previous revisions of the data.\015\012", sizeof(buf
)-strlen(buf
)-1);
531 strncat(buf
, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf
)-strlen(buf
)-1);
532 strncat(buf
, "which come after it.\015\012", sizeof(buf
)-strlen(buf
)-1);
535 INIT(&msg
, mail_string
, (void *)buf
, strlen(buf
));
536 if(!mail_append(stream
, mailbox
, &msg
))
544 * Add a message to the folder with the contents of the local data
548 append_data(stream
, mailbox
, special_hdr
, fp
)
555 char buf
[20000], *sto
, *p
;
561 if(fstat(fileno(fp
), &sbuf
) != 0){
562 fprintf(stderr
, "fstat of local file failed\n");
566 filelen
= (long) sbuf
.st_size
;
568 rbuf
.f
= dummy_soutr
;
572 rbuf
.end
= buf
+sizeof(buf
)-1;
573 write_fake_headers(&rbuf
, "Pine Remote Data Container", special_hdr
,
577 buf
[sizeof(buf
)-1] = '\0';
579 /* very conservative estimate of space needed */
580 len
= filelen
+ filelen
+ strlen(buf
) + 10;
581 sto
= fs_get((len
+1) * sizeof(char));
583 strncpy(sto
, buf
, len
);
585 p
= sto
+ strlen(sto
);
586 /* Write the contents */
587 while((c
= getc(fp
)) != EOF
){
589 * c-client expects CRLF-terminated lines. These lines
590 * can be either CRLF- or LF-terminated. We have to convert them
591 * when we copy into c-client.
593 if(c
== '\r' || c
== '\n'){
594 if(c
== '\r' && ((nextc
= getc(fp
)) != '\n') && nextc
!= EOF
)
604 else if(p
- sto
< len
)
614 INIT(&msg
, mail_string
, (void *)sto
, strlen(sto
));
615 if(!mail_append(stream
, mailbox
, &msg
)){
616 fprintf(stderr
, "Copy failed\n");
620 fs_give((void **)&sto
);
627 * Trim the number of saved copies of the remote data history in case
628 * this is the only way this folder is ever updated. We leave
629 * the first message there because it is supposed to be an explanatory
630 * message, but we don't actually check to see whether or not it is
631 * such a message or not.
634 trim_data(stream
, trimsize
)
638 if(stream
->nmsgs
> trimsize
+ 1){
642 snprintf(sequence
, sizeof(sequence
), "2:%ld", stream
->nmsgs
- trimsize
);
643 mail_flag(stream
, sequence
, "\\DELETED", ST_SET
);
644 mail_expunge(stream
);
650 write_fake_headers(where
, subject
, subtype
, special_hdr
)
659 char date
[200], vers
[10];
661 fake_env
= (ENVELOPE
*)fs_get(sizeof(ENVELOPE
));
662 memset(fake_env
, 0, sizeof(ENVELOPE
));
663 fake_body
= (BODY
*)fs_get(sizeof(BODY
));
664 memset(fake_body
, 0, sizeof(BODY
));
665 fake_from
= (ADDRESS
*)fs_get(sizeof(ADDRESS
));
666 memset(fake_from
, 0, sizeof(ADDRESS
));
669 fake_env
->subject
= cpystr(subject
);
670 fake_env
->date
= (unsigned char *) cpystr(date
);
671 fake_from
->personal
= cpystr("Pine Remote Data");
672 fake_from
->mailbox
= cpystr("nobody");
673 fake_from
->host
= cpystr("nowhere");
674 fake_env
->from
= fake_from
;
675 fake_body
->type
= REMOTE_DATA_TYPE
;
676 fake_body
->subtype
= cpystr(subtype
);
678 snprintf(vers
, sizeof(vers
), "%d", REMOTE_DATA_VERS_NUM
);
680 /* re-use subtype for special header name, too */
681 rfc822_output_header_line(where
, special_hdr
, 0L, vers
);
682 rfc822_output_header(where
, fake_env
, fake_body
, NULL
, 0L);
683 mail_free_envelope(&fake_env
);
684 mail_free_body(&fake_body
);
692 return((char *) strerror(err
));
696 void mm_exists(stream
, number
)
698 unsigned long number
;
703 void mm_expunged(stream
, number
)
705 unsigned long number
;
710 void mm_flags(stream
, number
)
712 unsigned long number
;
717 void mm_list(stream
, delim
, name
, attrib
)
726 void mm_lsub(stream
, delimiter
, name
, attributes
)
735 void mm_notify(stream
, string
, errflg
)
740 mm_log(string
, errflg
);
744 void mm_log(string
, errflg
)
757 fprintf(stderr
, "PARSE: %s\n", string
);
761 fprintf(stderr
, "WARN: %s\n", string
);
765 fprintf(stderr
, "ERROR: %s\n", string
);
769 fprintf(stderr
, "%s\n", string
);
774 void mm_login_method(mb
, user
, pwd
, trial
, method
)
781 mm_login(mb
, user
, (char **) pwd
, trial
);
784 void mm_login(mb
, user
, pwd
, trial
)
790 char prompt
[100], *last
, tmp
[MAILTMPLEN
];
791 int i
, j
, goal
, ugoal
, len
, rc
, flags
= 0;
792 #define NETMAXPASSWD 100
794 user
[NETMAXUSER
-1] = '\0';
797 if(mb
->user
&& *mb
->user
){
798 strncpy(user
, mb
->user
, NETMAXUSER
);
799 user
[NETMAXUSER
-1] = '\0';
804 /* Dress up long hostnames */
805 snprintf(prompt
, sizeof(prompt
), "%sHOST: ",
806 (mb
->sslflag
||mb
->tlsflag
) ? "+ " : "");
807 len
= strlen(prompt
);
808 /* leave space for "HOST", "ENTER NAME", and 15 chars for input name */
809 goal
= 80 - (len
+ 20 + MIN(15, 80/5));
810 last
= " ENTER LOGIN NAME: ";
813 if((goal
+= 13) < 9){
821 i
< sizeof(prompt
) && (prompt
[i
] = mb
->host
[j
]); i
++, j
++)
822 if(i
== goal
&& mb
->host
[goal
+1] && i
< sizeof(prompt
)){
823 strncpy(&prompt
[i
-3], "...", sizeof(prompt
)-(i
-3));
824 prompt
[sizeof(prompt
)-1] = '\0';
831 strncpy(&prompt
[i
], last
, sizeof(prompt
)-i
);
832 prompt
[sizeof(prompt
)-1] = '\0';
835 rc
= opt_enter(user
, NETMAXUSER
, prompt
, &flags
);
840 if(rc
== 1 || !user
[0]) {
845 strncpy(user
, mb
->user
, NETMAXUSER
);
847 user
[NETMAXUSER
-1] = '\0';
848 // pwd[NETMAXPASSWD-1] = '\0';
854 /* Dress up long host/user names */
855 /* leave space for "HOST", "USER" "ENTER PWD", 12 for user 6 for pwd */
856 snprintf(prompt
, sizeof(prompt
), "%sHOST: ", (mb
->sslflag
||mb
->tlsflag
) ? "+ " : "");
857 len
= strlen(prompt
);
858 goal
= strlen(mb
->host
);
859 ugoal
= strlen(user
);
860 if((i
= 80 - (len
+ 8 + 18 + 6)) < 14){
861 goal
= 0; /* no host! */
862 if((i
= 80 - (6 + 18 + 6)) <= 6){
863 ugoal
= 0; /* no user! */
864 if((i
= 80 - (18 + 6)) <= 0)
868 ugoal
= i
; /* whatever's left */
873 while(goal
+ ugoal
> i
)
880 snprintf(prompt
, sizeof(prompt
), "%sHOST: ",
881 (mb
->sslflag
||mb
->tlsflag
) ? "+ " : "");
883 i
< sizeof(prompt
) && (prompt
[i
] = mb
->host
[j
]); i
++, j
++)
884 if(i
== goal
&& mb
->host
[goal
+1] && i
< sizeof(prompt
)){
885 strncpy(&prompt
[i
-3], "...", sizeof(prompt
)-(i
-3));
886 prompt
[sizeof(prompt
)-1] = '\0';
894 strncpy(&prompt
[i
], &" USER: "[i
? 0 : 2], sizeof(prompt
)-i
);
895 prompt
[sizeof(prompt
)-1] = '\0';
896 for(i
+= strlen(&prompt
[i
]), j
= 0;
897 i
< sizeof(prompt
) && (prompt
[i
] = user
[j
]); i
++, j
++)
898 if(j
== ugoal
&& user
[ugoal
+1] && i
< sizeof(prompt
)){
899 strncpy(&prompt
[i
-3], "...", sizeof(prompt
)-(i
-3));
900 prompt
[sizeof(prompt
)-1] = '\0';
905 strncpy(&prompt
[i
], &" ENTER PASSWORD: "[i
? 0 : 8], sizeof(prompt
)-i
);
906 prompt
[sizeof(prompt
)-1] = '\0';
911 rc
= opt_enter(tmp
, NETMAXPASSWD
, prompt
, &flags
);
916 if(tmp
[0]) *pwd
= cpystr(tmp
);
917 if(rc
== 1 || !tmp
[0]) {
924 void mm_critical(stream
)
930 void mm_nocritical(stream
)
936 long mm_diskerror(stream
, errcode
, serious
)
945 void mm_fatal(string
)
948 fprintf(stderr
, "%s\n", string
);
952 void mm_searched(stream
, msgno
)
959 void mm_status(stream
, mailbox
, status
)
969 fprintf(stderr
, "%s\n", string
);
974 opt_enter(string
, field_len
, prompt
, flags
)
975 char *string
, *prompt
;
982 while(return_v
== -10){
984 if(flags
&& *flags
& OE_PASSWD
){
985 if((pw
= getpass(prompt
)) != NULL
){
986 if(strlen(pw
) < field_len
){
987 strncpy(string
, pw
, field_len
);
988 string
[field_len
-1] = '\0';
992 fputs("Password too long\n", stderr
);
997 return_v
= 1; /* cancel? */
1002 fputs(prompt
, stdout
);
1003 if(!fgets(string
, field_len
, stdin
))
1004 return_v
= 1; /* cancel? */
1005 string
[field_len
-1] = '\0';
1006 if((p
= strpbrk(string
, "\r\n")) != NULL
)