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);
48 app_main (int argc
, char argv
[])
56 * rpload [-s trimSize] [-f] -t Type -l Local_file -r Remote_folder
58 * Type is one of abook
61 * sig (this is mostly obsolete, literal sigs
62 * should be used instead)
63 * -f means force the folder to be written even if it
64 * doesn't look like it is of the right format
66 * Note: We're not worrying about memory leaks.
69 main(int argc
, char *argv
[])
71 MAILSTREAM
*stream
= NULL
;
73 int delete_existing
= 0, usage
= 0;
74 int force
= 0, trimsize
= 0;
75 char *local
= NULL
, *remote
= NULL
, *special_hdr
= NULL
;
76 RemoteType rt
, type
= NotSet
;
78 #include "../c-client/linkage.c"
80 if(parse_args(argc
, argv
, &force
, &trimsize
, &local
, &remote
, &type
)){
81 fprintf(stderr
, ustr
, argv
[0]);
85 if(!local
|| !*local
){
86 fprintf(stderr
, "No local file specified\n");
90 if(!remote
|| !*remote
){
91 fprintf(stderr
, "No remote folder specified\n");
96 fprintf(stderr
, "Type must be set to one of:\n");
97 for(rt
= Pinerc
; rt
!= NotSet
; rt
++)
98 fprintf(stderr
, " %s\n", ptype(rt
));
104 fprintf(stderr
, ustr
, argv
[0]);
108 if(!IS_REMOTE(remote
)){
110 "Remote folder name \"%s\" %s\n", remote
,
111 (*remote
!= '{') ? "must begin with \"{\"" : "not valid");
115 if(IS_REMOTE(local
)){
116 fprintf(stderr
, "Argument to -l (%s) must be a local filename", local
);
120 if(access(local
, ACCESS_EXISTS
) != 0){
121 fprintf(stderr
, "Local file \"%s\" does not exist\n", local
);
125 if(access(local
, READ_ACCESS
) != 0){
127 "Can't read local file \"%s\": %s\n",
128 local
, err_desc(errno
));
133 * Try opening the local file.
135 if((fp
= fopen(local
, "r")) == NULL
){
136 fprintf(stderr
, "Can't open \"%s\": %s\n", local
, err_desc(errno
));
141 * Try opening the remote folder. If it doesn't exist, create it.
144 /* failure would be normal here, so don't show it */
146 stream
= mail_open(NULL
, remote
, 0L);
147 if(!stream
|| stream
->halfopen
){
148 if(stream
&& stream
->halfopen
){
150 if(!mail_create(stream
, remote
))
153 stream
= mail_open(stream
, remote
, 0L);
154 if(!stream
|| stream
->halfopen
)
158 fprintf(stderr
, "Trouble opening remote folder \"%s\"\n", remote
);
166 fprintf(stderr
, "Remote folder \"%s\" is not writable\n", remote
);
170 if(stream
->nmsgs
> 0){
172 * There is a first message already. Check to see if it is one of
173 * our special header messages.
175 rt
= check_for_header_msg(stream
);
180 fprintf(stderr
, "Folder \"%s\"\ndoes not appear to be an Alpine remote \"%s\" folder.\nUse -f to force.\n", remote
, ptype(type
));
181 fprintf(stderr
, "-f will cause %ld messages to be deleted\n",
190 fprintf(stderr
, "Folder \"%s\" is type \"%s\"\nUse -f to force switch.\n", remote
, ptype(rt
));
191 fprintf(stderr
, "-f will cause %ld messages to be deleted\n",
202 snprintf(sequence
, sizeof(sequence
), "1:%ld", stream
->nmsgs
);
203 mail_flag(stream
, sequence
, "\\DELETED", ST_SET
);
204 mail_expunge(stream
);
208 special_hdr
= spechdr(type
);
211 * Add the explanatory header message if needed.
213 if(stream
->nmsgs
== 0){
214 if(add_initial_msg(stream
, remote
, special_hdr
) != 0){
221 * Add the actual data in a message.
223 if(append_data(stream
, remote
, special_hdr
, fp
) != 0){
229 * Trim the size of the remote folder.
232 trim_data(stream
, trimsize
);
240 check_for_header_msg(MAILSTREAM
*stream
)
246 char *pinerc
, *abook
, *sig
, *smime
;
248 pinerc
= spechdr(Pinerc
);
249 abook
= spechdr(Abook
);
251 smime
= spechdr(Smime
);
253 len
= MAX(MAX(strlen(pinerc
), strlen(abook
)), MAX(strlen(sig
), strlen(smime
)));
255 sl
= mail_newstringlist();
256 sl
->text
.data
= (unsigned char *)fs_get((len
+1) * sizeof(unsigned char));
258 strncpy((char *)sl
->text
.data
, try, len
);
259 sl
->text
.data
[len
] = '\0';
260 sl
->text
.size
= strlen((char *) sl
->text
.data
);
262 if(stream
&& (h
=mail_fetch_header(stream
,1L,NULL
,sl
,NULL
,FT_PEEK
))){
264 if(strlen(h
) >= sl
->text
.size
&& !struncmp(h
, try, sl
->text
.size
))
270 strncpy((char *)sl
->text
.data
, try, len
);
271 sl
->text
.data
[len
] = '\0';
272 sl
->text
.size
= strlen((char *) sl
->text
.data
);
273 if(stream
&& (h
=mail_fetch_header(stream
,1L,NULL
,sl
,NULL
,FT_PEEK
))){
275 if(strlen(h
) >= sl
->text
.size
&& !struncmp(h
, try, sl
->text
.size
))
282 strncpy((char *)sl
->text
.data
, try, len
);
283 sl
->text
.data
[len
] = '\0';
284 sl
->text
.size
= strlen((char *) sl
->text
.data
);
285 if(stream
&& (h
=mail_fetch_header(stream
,1L,NULL
,sl
,NULL
,FT_PEEK
))){
287 if(strlen(h
) >= sl
->text
.size
&& !struncmp(h
, try, sl
->text
.size
))
293 mail_free_stringlist(&sl
);
296 fs_give((void **)&pinerc
);
298 fs_give((void **)&abook
);
300 fs_give((void **)&sig
);
302 fs_give((void **)&smime
);
309 ptype(RemoteType rtype
)
315 ret
= cpystr("pinerc");
318 ret
= cpystr("abook");
324 ret
= cpystr("smime");
335 spechdr(RemoteType rtype
)
341 ret
= cpystr(REMOTE_PINERC_SUBTYPE
);
344 ret
= cpystr(REMOTE_ABOOK_SUBTYPE
);
347 ret
= cpystr(REMOTE_SIG_SUBTYPE
);
350 ret
= cpystr(REMOTE_SMIME_SUBTYPE
);
361 parse_args(int argc
, char **argv
, int *force
, int *trimsize
, char **local
, char **remote
, RemoteType
*type
)
373 /* while more arguments with leading - */
374 Loop
: while(--ac
> 0 && **++av
== '-'){
375 /* while more chars in this argument */
385 case 't': case 'l': /* string args */
387 case 's': /* integer args */
393 fprintf(stderr
, "missing argument for flag \"%c\"\n", c
);
410 for(rt
= Pinerc
; rt
!= NotSet
; rt
++){
411 if(!strucmp(str
, ptype(rt
)))
418 if(!isdigit((unsigned char)str
[0])){
420 "non-numeric argument for flag \"%c\"\n", c
);
425 *trimsize
= atoi(str
);
427 fprintf(stderr
, "trimsize of %d is too small, have to leave at least one copy\n", *trimsize
);
430 else if(*trimsize
> 100){
432 "trimsize of %d is too large, 5 or 10 is sufficient\n",
443 fprintf(stderr
, "unknown flag \"%c\"\n", c
);
458 dummy_soutr(void *stream
, char *string
)
465 * Add an explanatory first message advising user that this is a
466 * special sort of folder.
469 add_initial_msg(MAILSTREAM
*stream
, char *mailbox
, char *special_hdr
)
475 rbuf
.f
= dummy_soutr
;
479 rbuf
.end
= buf
+sizeof(buf
)-1;
480 write_fake_headers(&rbuf
, "Header Message for Remote Data", "plain", special_hdr
);
483 buf
[sizeof(buf
)-1] = '\0';
485 if(!strucmp(special_hdr
, REMOTE_ABOOK_SUBTYPE
)){
486 strncat(buf
, "This folder contains a single Alpine addressbook.\015\012", sizeof(buf
)-strlen(buf
)-1);
487 strncat(buf
, "This message is just an explanatory message.\015\012", sizeof(buf
)-strlen(buf
)-1);
488 strncat(buf
, "The last message in the folder is the live addressbook data.\015\012", sizeof(buf
)-strlen(buf
)-1);
489 strncat(buf
, "The rest of the messages contain previous revisions of the addressbook data.\015\012", sizeof(buf
)-strlen(buf
)-1);
490 strncat(buf
, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf
)-strlen(buf
)-1);
491 strncat(buf
, "which come after it.\015\012", sizeof(buf
)-strlen(buf
)-1);
493 else if(!strucmp(special_hdr
, REMOTE_PINERC_SUBTYPE
)){
494 strncat(buf
, "This folder contains an Alpine config file.\015\012", sizeof(buf
)-strlen(buf
)-1);
495 strncat(buf
, "This message is just an explanatory message.\015\012", sizeof(buf
)-strlen(buf
)-1);
496 strncat(buf
, "The last message in the folder is the live config data.\015\012", sizeof(buf
)-strlen(buf
)-1);
497 strncat(buf
, "The rest of the messages contain previous revisions of the data.\015\012", sizeof(buf
)-strlen(buf
)-1);
498 strncat(buf
, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf
)-strlen(buf
)-1);
499 strncat(buf
, "which come after it.\015\012", sizeof(buf
)-strlen(buf
)-1);
501 else if(!strucmp(special_hdr
, REMOTE_SMIME_SUBTYPE
)){
502 strncat(buf
, "This folder contains Alpine S/MIME config information.\015\012", sizeof(buf
)-strlen(buf
)-1);
503 strncat(buf
, "This message is just an explanatory message.\015\012", sizeof(buf
)-strlen(buf
)-1);
504 strncat(buf
, "The last message in the folder is the live data.\015\012", sizeof(buf
)-strlen(buf
)-1);
505 strncat(buf
, "The rest of the messages contain previous revisions of the data.\015\012", sizeof(buf
)-strlen(buf
)-1);
506 strncat(buf
, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf
)-strlen(buf
)-1);
507 strncat(buf
, "which come after it.\015\012", sizeof(buf
)-strlen(buf
)-1);
510 strncat(buf
, "This folder contains remote Alpine data.\015\012", sizeof(buf
)-strlen(buf
)-1);
511 strncat(buf
, "This message is just an explanatory message.\015\012", sizeof(buf
)-strlen(buf
)-1);
512 strncat(buf
, "The last message in the folder is the live data.\015\012", sizeof(buf
)-strlen(buf
)-1);
513 strncat(buf
, "The rest of the messages contain previous revisions of the data.\015\012", sizeof(buf
)-strlen(buf
)-1);
514 strncat(buf
, "To restore a previous revision just delete and expunge all of the messages\015\012", sizeof(buf
)-strlen(buf
)-1);
515 strncat(buf
, "which come after it.\015\012", sizeof(buf
)-strlen(buf
)-1);
518 INIT(&msg
, mail_string
, (void *)buf
, strlen(buf
));
519 if(!mail_append(stream
, mailbox
, &msg
))
527 * Add a message to the folder with the contents of the local data
531 append_data(MAILSTREAM
*stream
, char *mailbox
, char *special_hdr
, FILE *fp
)
534 char buf
[20000], *sto
, *p
;
540 if(fstat(fileno(fp
), &sbuf
) != 0){
541 fprintf(stderr
, "fstat of local file failed\n");
545 filelen
= (long) sbuf
.st_size
;
547 rbuf
.f
= dummy_soutr
;
551 rbuf
.end
= buf
+sizeof(buf
)-1;
552 write_fake_headers(&rbuf
, "Pine Remote Data Container", special_hdr
,
556 buf
[sizeof(buf
)-1] = '\0';
558 /* very conservative estimate of space needed */
559 len
= filelen
+ filelen
+ strlen(buf
) + 10;
560 sto
= fs_get((len
+1) * sizeof(char));
562 strncpy(sto
, buf
, len
);
564 p
= sto
+ strlen(sto
);
565 /* Write the contents */
566 while((c
= getc(fp
)) != EOF
){
568 * c-client expects CRLF-terminated lines. These lines
569 * can be either CRLF- or LF-terminated. We have to convert them
570 * when we copy into c-client.
572 if(c
== '\r' || c
== '\n'){
573 if(c
== '\r' && ((nextc
= getc(fp
)) != '\n') && nextc
!= EOF
)
583 else if(p
- sto
< len
)
593 INIT(&msg
, mail_string
, (void *)sto
, strlen(sto
));
594 if(!mail_append(stream
, mailbox
, &msg
)){
595 fprintf(stderr
, "Copy failed\n");
599 fs_give((void **)&sto
);
606 * Trim the number of saved copies of the remote data history in case
607 * this is the only way this folder is ever updated. We leave
608 * the first message there because it is supposed to be an explanatory
609 * message, but we don't actually check to see whether or not it is
610 * such a message or not.
613 trim_data(MAILSTREAM
*stream
, int trimsize
)
615 if(stream
->nmsgs
> trimsize
+ 1){
619 snprintf(sequence
, sizeof(sequence
), "2:%ld", stream
->nmsgs
- trimsize
);
620 mail_flag(stream
, sequence
, "\\DELETED", ST_SET
);
621 mail_expunge(stream
);
627 write_fake_headers(RFC822BUFFER
*where
, char *subject
, char *subtype
, char *special_hdr
)
632 char date
[200], vers
[10];
634 fake_env
= (ENVELOPE
*)fs_get(sizeof(ENVELOPE
));
635 memset(fake_env
, 0, sizeof(ENVELOPE
));
636 fake_body
= (BODY
*)fs_get(sizeof(BODY
));
637 memset(fake_body
, 0, sizeof(BODY
));
638 fake_from
= (ADDRESS
*)fs_get(sizeof(ADDRESS
));
639 memset(fake_from
, 0, sizeof(ADDRESS
));
642 fake_env
->subject
= cpystr(subject
);
643 fake_env
->date
= (unsigned char *) cpystr(date
);
644 fake_from
->personal
= cpystr("Pine Remote Data");
645 fake_from
->mailbox
= cpystr("nobody");
646 fake_from
->host
= cpystr("nowhere");
647 fake_env
->from
= fake_from
;
648 fake_body
->type
= REMOTE_DATA_TYPE
;
649 fake_body
->subtype
= cpystr(subtype
);
651 snprintf(vers
, sizeof(vers
), "%d", REMOTE_DATA_VERS_NUM
);
653 /* re-use subtype for special header name, too */
654 rfc822_output_header_line(where
, special_hdr
, 0L, vers
);
655 rfc822_output_header(where
, fake_env
, fake_body
, NULL
, 0L);
656 mail_free_envelope(&fake_env
);
657 mail_free_body(&fake_body
);
664 return((char *) strerror(err
));
668 void mm_exists(MAILSTREAM
*stream
, unsigned long number
)
673 void mm_expunged(MAILSTREAM
*stream
, unsigned long number
)
678 void mm_flags(MAILSTREAM
*stream
, unsigned long number
)
683 void mm_list(MAILSTREAM
*stream
, int delim
, char *name
, long attrib
)
688 void mm_lsub(MAILSTREAM
*stream
, int delimiter
, char *name
, long attributes
)
693 void mm_notify(MAILSTREAM
*stream
, char *string
, long errflg
)
695 mm_log(string
, errflg
);
699 void mm_log(char *string
, long errflg
)
710 fprintf(stderr
, "PARSE: %s\n", string
);
714 fprintf(stderr
, "WARN: %s\n", string
);
718 fprintf(stderr
, "ERROR: %s\n", string
);
722 fprintf(stderr
, "%s\n", string
);
727 void mm_login_method(NETMBX
*mb
, char *user
, void *pwd
, long trial
, char *method
)
729 mm_login(mb
, user
, (char **) pwd
, trial
);
732 void mm_login(NETMBX
*mb
, char *user
, char **pwd
, long trial
)
734 char prompt
[100], *last
, tmp
[MAILTMPLEN
];
735 int i
, j
, goal
, ugoal
, len
, rc
, flags
= 0;
736 #define NETMAXPASSWD 100
738 user
[NETMAXUSER
-1] = '\0';
741 if(mb
->user
&& *mb
->user
){
742 strncpy(user
, mb
->user
, NETMAXUSER
);
743 user
[NETMAXUSER
-1] = '\0';
748 /* Dress up long hostnames */
749 snprintf(prompt
, sizeof(prompt
), "%sHOST: ",
750 (mb
->sslflag
||mb
->tlsflag
) ? "+ " : "");
751 len
= strlen(prompt
);
752 /* leave space for "HOST", "ENTER NAME", and 15 chars for input name */
753 goal
= 80 - (len
+ 20 + MIN(15, 80/5));
754 last
= " ENTER LOGIN NAME: ";
757 if((goal
+= 13) < 9){
765 i
< sizeof(prompt
) && (prompt
[i
] = mb
->host
[j
]); i
++, j
++)
766 if(i
== goal
&& mb
->host
[goal
+1] && i
< sizeof(prompt
)){
767 strncpy(&prompt
[i
-3], "...", sizeof(prompt
)-(i
-3));
768 prompt
[sizeof(prompt
)-1] = '\0';
775 strncpy(&prompt
[i
], last
, sizeof(prompt
)-i
);
776 prompt
[sizeof(prompt
)-1] = '\0';
779 rc
= opt_enter(user
, NETMAXUSER
, prompt
, &flags
);
784 if(rc
== 1 || !user
[0]) {
789 strncpy(user
, mb
->user
, NETMAXUSER
);
791 user
[NETMAXUSER
-1] = '\0';
792 // pwd[NETMAXPASSWD-1] = '\0';
798 /* Dress up long host/user names */
799 /* leave space for "HOST", "USER" "ENTER PWD", 12 for user 6 for pwd */
800 snprintf(prompt
, sizeof(prompt
), "%sHOST: ", (mb
->sslflag
||mb
->tlsflag
) ? "+ " : "");
801 len
= strlen(prompt
);
802 goal
= strlen(mb
->host
);
803 ugoal
= strlen(user
);
804 if((i
= 80 - (len
+ 8 + 18 + 6)) < 14){
805 goal
= 0; /* no host! */
806 if((i
= 80 - (6 + 18 + 6)) <= 6){
807 ugoal
= 0; /* no user! */
808 if((i
= 80 - (18 + 6)) <= 0)
812 ugoal
= i
; /* whatever's left */
817 while(goal
+ ugoal
> i
)
824 snprintf(prompt
, sizeof(prompt
), "%sHOST: ",
825 (mb
->sslflag
||mb
->tlsflag
) ? "+ " : "");
827 i
< sizeof(prompt
) && (prompt
[i
] = mb
->host
[j
]); i
++, j
++)
828 if(i
== goal
&& mb
->host
[goal
+1] && i
< sizeof(prompt
)){
829 strncpy(&prompt
[i
-3], "...", sizeof(prompt
)-(i
-3));
830 prompt
[sizeof(prompt
)-1] = '\0';
838 strncpy(&prompt
[i
], &" USER: "[i
? 0 : 2], sizeof(prompt
)-i
);
839 prompt
[sizeof(prompt
)-1] = '\0';
840 for(i
+= strlen(&prompt
[i
]), j
= 0;
841 i
< sizeof(prompt
) && (prompt
[i
] = user
[j
]); i
++, j
++)
842 if(j
== ugoal
&& user
[ugoal
+1] && i
< sizeof(prompt
)){
843 strncpy(&prompt
[i
-3], "...", sizeof(prompt
)-(i
-3));
844 prompt
[sizeof(prompt
)-1] = '\0';
849 strncpy(&prompt
[i
], &" ENTER PASSWORD: "[i
? 0 : 8], sizeof(prompt
)-i
);
850 prompt
[sizeof(prompt
)-1] = '\0';
855 rc
= opt_enter(tmp
, NETMAXPASSWD
, prompt
, &flags
);
860 if(tmp
[0]) *pwd
= cpystr(tmp
);
861 if(rc
== 1 || !tmp
[0]) {
868 void mm_critical(MAILSTREAM
*stream
)
873 void mm_nocritical(MAILSTREAM
*stream
)
878 long mm_diskerror(MAILSTREAM
*stream
, long errcode
, long serious
)
884 void mm_fatal(char *string
)
886 fprintf(stderr
, "%s\n", string
);
890 void mm_searched(MAILSTREAM
*stream
, unsigned long msgno
)
895 void mm_status(MAILSTREAM
*stream
, char *mailbox
, MAILSTATUS
*status
)
899 void mm_dlog(char *string
)
901 fprintf(stderr
, "%s\n", string
);
906 opt_enter(char *string
, int field_len
, char *prompt
, int *flags
)
911 while(return_v
== -10){
913 if(flags
&& *flags
& OE_PASSWD
){
914 if((pw
= getpass(prompt
)) != NULL
){
915 if(strlen(pw
) < field_len
){
916 strncpy(string
, pw
, field_len
);
917 string
[field_len
-1] = '\0';
921 fputs("Password too long\n", stderr
);
926 return_v
= 1; /* cancel? */
931 fputs(prompt
, stdout
);
932 if(!fgets(string
, field_len
, stdin
))
933 return_v
= 1; /* cancel? */
934 string
[field_len
-1] = '\0';
935 if((p
= strpbrk(string
, "\r\n")) != NULL
)