1 /*
2  * lftp - file transfer program
3  *
4  * Copyright (c) 1996-2013 by Alexander V. Lukyanov (lav@yars.free.net)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 
22 #include "modconfig.h"
23 
24 #include "trio.h"
25 #include <sys/types.h>
26 #include <sys/stat.h> // for mkdir()
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <locale.h>
32 #include <ctype.h>
33 
34 #include "xstring.h"
35 #include "xmalloc.h"
36 #include "alias.h"
37 #include "CmdExec.h"
38 #include "SignalHook.h"
39 #include "GetPass.h"
40 #include "History.h"
41 #include "log.h"
42 #include "DummyProto.h"
43 #include "ResMgr.h"
44 #include "LsCache.h"
45 #include "IdNameCache.h"
46 #include "LocalDir.h"
47 #include "ConnectionSlot.h"
48 #include "misc.h"
49 #include "ArgV.h"
50 #include "attach.h"
51 
52 #include "configmake.h"
53 
54 #include "lftp_rl.h"
55 #include "complete.h"
56 
57 CDECL_BEGIN
58 #include <glob.h>
59 CDECL_END
60 
61 #define top_exec CmdExec::top
62 
hook_signals()63 void  hook_signals()
64 {
65    SignalHook::DoCount(SIGHUP);
66    SignalHook::Ignore(SIGTTOU);
67    ProcWait::Signal(true);
68 }
69 
70 ResDecl res_save_cwd_history
71    ("cmd:save-cwd-history","yes",ResMgr::BoolValidate,ResMgr::NoClosure);
72 ResDecl res_save_rl_history
73    ("cmd:save-rl-history","yes",ResMgr::BoolValidate,ResMgr::NoClosure);
74 ResDecl res_stifle_rl_history
75    ("cmd:stifle-rl-history","500",ResMgr::UNumberValidate,ResMgr::NoClosure);
76 
77 class ReadlineFeeder : public CmdFeeder, private ResClient
78 {
79    bool tty:1;
80    bool ctty:1;
81    bool add_newline:1;
82    int eof_count;
83    xstring_c cmd_buf;
84    xstring_c for_history;
85 
86    static bool readline_inited;
readline_init()87    void readline_init()
88    {
89       if(readline_inited)
90 	 return;
91       readline_inited=true;
92       lftp_readline_init();
93       lftp_rl_read_history();
94       if(for_history)
95       {
96 	 lftp_add_history_nodups(for_history);
97 	 for_history.set(0);
98       }
99       Reconfig(0);
100    }
101 
102 public:
ReadlineFeeder(const ArgV * args)103    ReadlineFeeder(const ArgV *args)
104    {
105       tty=isatty(0);
106       ctty=(tcgetpgrp(0)!=(pid_t)-1);
107       add_newline=false;
108       eof_count=0;
109       if(args && args->count()>1)
110 	 args->CombineQuotedTo(for_history);
111    }
~ReadlineFeeder()112    virtual ~ReadlineFeeder()
113    {
114       if(readline_inited)
115       {
116 	 if(res_save_cwd_history.QueryBool(0))
117 	    cwd_history.Save();
118 	 if(res_save_rl_history.QueryBool(0))
119 	    lftp_rl_write_history();
120       }
121    }
IsInteractive() const122    bool IsInteractive() const
123    {
124       return tty;
125    }
RealEOF()126    bool RealEOF()
127    {
128       return !tty || eof_count>3;
129    }
130 
NextCmd(class CmdExec * exec,const char * prompt)131    const char *NextCmd(class CmdExec *exec,const char *prompt)
132    {
133       if(add_newline)
134       {
135 	 add_newline=false;
136 	 return "\n";
137       }
138 
139       ::completion_shell=exec;
140       ::remote_completion=exec->remote_completion;
141 
142       if(tty)
143       {
144 	 readline_init();
145 
146 	 if(ctty) // controlling terminal
147 	 {
148 	    if(!in_foreground_pgrp())
149 	    {
150 	       // looks like we are in background. Can't read from tty
151 	       exec->Timeout(500);
152 	       return "";
153 	    }
154 	 }
155 
156 	 SignalHook::ResetCount(SIGINT);
157 	 cmd_buf.set_allocated(lftp_readline(prompt));
158 	 xmalloc_register_block(cmd_buf.get_non_const());
159 	 if(cmd_buf && *cmd_buf)
160 	 {
161 	    if(exec->csh_history)
162 	    {
163 	       char *history_value0=0;
164 	       int expanded = lftp_history_expand (cmd_buf, &history_value0);
165 	       if (expanded)
166 	       {
167 		  if(history_value0)
168 		     xmalloc_register_block(history_value0);
169 		  xstring_ca history_value(history_value0);
170 
171 		  if (expanded < 0)
172 		     fprintf (stderr, "%s\n", history_value.get());
173 
174 		  /* If there was an error, return nothing. */
175 		  if (expanded < 0 || expanded == 2)	/* 2 == print only */
176 		  {
177 		     exec->Timeout(0);  // and retry immediately
178 		     return "";
179 		  }
180 
181 		  cmd_buf.move_here(history_value);
182 	       }
183 	    }
184 	    lftp_add_history_nodups(cmd_buf);
185 	 }
186 	 else if(cmd_buf==0 && exec->interactive)
187 	    puts("exit");
188 
189 	 if(cmd_buf==0)
190 	    eof_count++;
191 	 else
192 	    eof_count=0;
193       }
194       else // not a tty
195       {
196 	 if(exec->interactive)
197 	 {
198 	    while(*prompt)
199 	    {
200 	       char ch=*prompt++;
201 	       if(ch!=1 && ch!=2)
202 		  putchar(ch);
203 	    }
204 	    fflush(stdout);
205 	 }
206 	 cmd_buf.set_allocated(readline_from_file(0));
207       }
208 
209       ::completion_shell=0;
210 
211       if(cmd_buf && last_char(cmd_buf)!='\n')
212       {
213 	 exec->Timeout(0);
214 	 add_newline=true;
215       }
216       return cmd_buf;
217    }
clear()218    void clear()
219       {
220 	 if(!tty)
221 	    return;
222 	 lftp_rl_clear();
223       }
Reconfig(const char *)224    void Reconfig(const char *) {
225       lftp_rl_history_stifle(res_stifle_rl_history.Query(0));
226    }
227 };
228 bool ReadlineFeeder::readline_inited;
229 
230 #define args	  (parent->args)
231 #define exit_code (parent->exit_code)
232 #define output	  (parent->output)
233 #define session	  (parent->session)
234 #define eprintf	  parent->eprintf
CMD(history)235 CMD(history)
236 {
237    enum { READ, WRITE, CLEAR, LIST } mode = LIST;
238    const char *fn = NULL;
239    static struct option history_options[]=
240    {
241       {"read",required_argument,0,'r'},
242       {"write",required_argument,0,'w'},
243       {"clear",no_argument,0,'c'},
244       {"list",required_argument,0,'l'},
245       {0,0,0,0}
246    };
247 
248    exit_code=0;
249    int opt;
250    while((opt=args->getopt_long("+r:w:cl",history_options,0))!=EOF) {
251       switch(opt) {
252       case 'r':
253 	 mode = READ;
254 	 fn = optarg;
255 	 break;
256       case 'w':
257 	 mode = WRITE;
258 	 fn = optarg;
259 	 break;
260       case 'c':
261 	 mode = CLEAR;
262 	 break;
263       case 'l':
264 	 mode = LIST;
265 	 break;
266       case '?':
267 	 eprintf(_("Try `help %s' for more information.\n"),args->a0());
268 	 return 0;
269       }
270    }
271 
272    int cnt = 16;
273    if(const char *arg = args->getcurr()) {
274       if(!strcasecmp(arg, "all"))
275 	 cnt = -1;
276       else if(isdigit((unsigned char)arg[0]))
277 	 cnt = atoi(arg);
278       else {
279 	 eprintf(_("%s: %s - not a number\n"), args->a0(), args->getcurr());
280 	 exit_code=1;
281 	 return 0;
282       }
283    }
284 
285    switch(mode) {
286    case READ:
287       if(int err = lftp_history_read(fn)) {
288 	 eprintf("%s: %s: %s\n", args->a0(), fn, strerror(err));
289 	 exit_code=1;
290       }
291       break;
292 
293    case WRITE:
294       if(int err = lftp_history_write(fn)) {
295 	 eprintf("%s: %s: %s\n", args->a0(), fn, strerror(err));
296 	 exit_code=1;
297       }
298       break;
299 
300    case LIST:
301       lftp_history_list(cnt);
302       break;
303    case CLEAR:
304       lftp_history_clear();
305       break;
306    }
307 
308    return 0;
309 }
CMD(attach)310 CMD(attach)
311 {
312    const char *pid_s=args->getarg(1);
313    if(!pid_s) {
314       xstring& path=AcceptTermFD::get_sock_path(1);
315       path.rtrim('1');
316       path.append('*');
317       glob_t g;
318       glob(path, 0, NULL, &g);
319       for(size_t i=0; i<g.gl_pathc; i++) {
320 	 const char *sock_path=g.gl_pathv[i];
321 	 pid_s=strrchr(sock_path,'-');
322 	 if(!pid_s)
323 	    continue;
324 	 pid_s++;
325 	 int p=atoi(pid_s);
326 	 if(p<=1) {
327 	    pid_s=0;
328 	    continue;
329 	 }
330 	 if(kill(p,0)==-1) {
331 	    if(errno==ESRCH) {
332 	       eprintf("%s: removing stale socket `%s'.\n",args->a0(),sock_path);
333 	       if(unlink(sock_path)==-1)
334 		  eprintf("%s: unlink(%s): %s\n",args->a0(),sock_path,strerror(errno));
335 	    }
336 	    pid_s=0;
337 	    continue;
338 	 }
339 	 pid_s=alloca_strdup(pid_s);
340 	 break;
341       }
342       globfree(&g);
343       if(!pid_s) {
344 	 eprintf("%s: no backgrounded lftp processes found.\n",args->a0());
345 	 return 0;
346       }
347    }
348    int pid=atoi(pid_s);
349    SMTaskRef<SendTermFD> term_sender(new SendTermFD(pid));
350    while(!term_sender->Done()) {
351       SMTask::Schedule();
352       SMTask::Block();
353    }
354    exit_code=0;
355    if(term_sender->Failed()) {
356       eprintf("%s\n",term_sender->ErrorText());
357       exit_code=1;
358    }
359    return 0;
360 }
361 #undef args
362 #undef exit_code
363 #undef output
364 #undef session
365 #undef eprintf
366 
367 
sig_term(int sig)368 static void sig_term(int sig)
369 {
370    printf(_("[%u] Terminated by signal %d. %s\n"),(unsigned)getpid(),sig,SMTask::now.IsoDateTime());
371    if(top_exec) {
372       top_exec->KillAll();
373       alarm(30);
374       while(Job::NumberOfJobs()>0) {
375 	 SMTask::Schedule();
376 	 SMTask::Block();
377       }
378    }
379    exit(1);
380 }
381 
detach()382 static void detach()
383 {
384    SignalHook::Ignore(SIGINT);
385    SignalHook::Ignore(SIGHUP);
386    SignalHook::Ignore(SIGTSTP);
387 
388    const char *home=get_lftp_data_dir();
389    if(home)
390    {
391       xstring& log=xstring::get_tmp(home);
392       if(access(log,F_OK)==-1)
393 	 log.append("_log");
394       else
395 	 log.append("/log");
396 
397       int fd=open(log,O_WRONLY|O_APPEND|O_CREAT,0600);
398       if(fd>=0)
399       {
400 	 dup2(fd,1); // stdout
401 	 dup2(fd,2); // stderr
402 	 if(fd!=1 && fd!=2)
403 	    close(fd);
404       }
405       const char *c="debug";
406       ResMgr::Set("log:show-pid",c,"yes");
407       ResMgr::Set("log:show-time",c,"yes");
408       ResMgr::Set("log:show-ctx",c,"yes");
409    }
410    close(0);	  // close stdin.
411    open("/dev/null",O_RDONLY); // reopen it, just in case.
412 
413 #ifdef HAVE_SETSID
414    setsid();	  // start a new session.
415 #endif
416 
417    SignalHook::Handle(SIGTERM,sig_term);
418 }
419 
move_to_background()420 static void move_to_background()
421 {
422    // notify jobs
423    Job::lftpMovesToBackground_ToAll();
424    // wait they do something, but no more than 1 sec.
425    SMTask::RollAll(TimeInterval(1,0));
426    // if all jobs terminated, don't really move to bg.
427    if(Job::NumberOfJobs()==0)
428       return;
429 
430    top_exec->AtBackground();
431    top_exec->WaitDone();
432    if(Job::NumberOfJobs()==0)
433       return;
434 
435    fflush(stdout);
436    fflush(stderr);
437 
438    pid_t pid=fork();
439    switch(pid)
440    {
441    case(0): // child
442    {
443       pid=getpid();
444       detach();
445       printf(_("[%u] Started.  %s\n"),(unsigned)pid,SMTask::now.IsoDateTime());
446       SMTaskRef<AcceptTermFD> term_acceptor(new AcceptTermFD());
447       for(;;)
448       {
449 	 SMTask::Schedule();
450 	 if(Job::NumberOfJobs()==0)
451 	    break;
452 	 SMTask::Block();
453 	 if(term_acceptor->Accepted()) {
454 	    hook_signals();
455 	    top_exec->SetInteractive();
456 	    top_exec->SetStatusLine(new StatusLine(1));
457 	    top_exec->SetCmdFeeder(new ReadlineFeeder(0));
458 	    for(;;)
459 	    {
460 	       SMTask::Schedule();
461 	       if(top_exec->Done() || term_acceptor->Detached()) {
462 		  if(Job::NumberOfJobs()>0) {
463 		     printf(_("[%u] Detaching from the terminal to complete transfers...\n"),(unsigned)pid);
464 		  } else if(top_exec->Done()) {
465 		     printf(_("[%u] Exiting and detaching from the terminal.\n"),(unsigned)pid);
466 		  }
467 		  fflush(stdout);
468 		  term_acceptor->Detach();
469 		  detach();
470 		  printf(_("[%u] Detached from terminal. %s\n"),(unsigned)pid,SMTask::now.IsoDateTime());
471 		  break;
472 	       }
473 	       SMTask::Block();
474 	    }
475 	 }
476       }
477       top_exec->AtExitBg();
478       top_exec->AtTerminate();
479       top_exec->WaitDone();
480       printf(_("[%u] Finished. %s\n"),(unsigned)pid,SMTask::now.IsoDateTime());
481       break;
482    }
483    default: // parent
484       printf(_("[%u] Moving to background to complete transfers...\n"),
485 	       (unsigned)pid);
486       fflush(stdout);
487       _exit(0);
488    case(-1):
489       perror("fork()");
490    }
491 }
492 
lftp_slot(int count,int key)493 int lftp_slot(int count,int key)
494 {
495    if(!top_exec)
496       return 0;
497    char slot[2];
498    slot[0]=key;
499    slot[1]=0;
500    top_exec->ChangeSlot(slot);
501    lftp_rl_set_prompt(top_exec->MakePrompt());
502    return 0;
503 }
504 
source_if_exist(const char * rc)505 void  source_if_exist(const char *rc)
506 {
507    if(access(rc,R_OK)!=-1)
508    {
509       top_exec->FeedCmd("source ");
510       top_exec->FeedCmd(rc);
511       top_exec->FeedCmd("\n");
512    }
513 }
514 
tty_clear()515 static void tty_clear()
516 {
517    if(top_exec)
518       top_exec->pre_stdout();
519 }
520 
521 // look for the option, remove it and return true if found
pick_option(int & argc,char ** argv,const char * option)522 static bool pick_option(int& argc,char **argv,const char *option)
523 {
524    for(int i=1; i<argc; i++) {
525       if(!strcmp(argv[i],option)) {
526 	 // remove the option, move trailing NULL too.
527 	 memmove(argv+i,argv+i+1,(argc-i)*sizeof(*argv));
528 	 argc--;
529 	 return true;
530       }
531       if(!strcmp(argv[i],"--"))	 // end of options
532 	 break;
533    }
534    return false;
535 }
536 
537 char *program_name;
538 
main(int argc,char ** argv)539 int   main(int argc,char **argv)
540 {
541    program_name=argv[0];
542 
543 #ifdef SOCKS4
544    SOCKSinit(program_name);
545 #endif
546 
547    setlocale (LC_ALL, "");
548    setlocale (LC_NUMERIC, "C");
549    bindtextdomain (PACKAGE, LOCALEDIR);
550    textdomain (PACKAGE);
551 
552    CmdExec::RegisterCommand("history",cmd_history,
553 	 N_("history -w file|-r file|-c|-l [cnt]"),
554 	 N_(" -w <file> Write history to file.\n"
555 	 " -r <file> Read history from file; appends to current history.\n"
556 	 " -c  Clear the history.\n"
557 	 " -l  List the history (default).\n"
558 	 "Optional argument cnt specifies the number of history lines to list,\n"
559 	 "or \"all\" to list all entries.\n"));
560    CmdExec::RegisterCommand("attach",cmd_attach,"attach [PID]",
561       N_("Attach the terminal to specified backgrounded lftp process.\n"));
562 
563    top_exec=new CmdExec(0,0);
564    hook_signals();
565    top_exec->SetStatusLine(new StatusLine(1));
566    Log::global=new Log("debug");
567    Log::global->SetCB(tty_clear);
568 
569    source_if_exist(SYSCONFDIR"/lftp.conf");
570 
571    if(!pick_option(argc,argv,"--norc")) {
572       const char *home=getenv("HOME");
573       if(home)
574 	 source_if_exist(dir_file(home,".lftprc"));
575       home=get_lftp_config_dir();
576       if(home)
577 	 source_if_exist(dir_file(home,"rc"));
578    }
579 
580    top_exec->WaitDone();
581    top_exec->SetTopLevel();
582    top_exec->Fg();
583 
584    Ref<ArgV> args(new ArgV(argc,argv));
585    args->setarg(0,"lftp");
586 
587    lftp_feeder=new ReadlineFeeder(args);
588 
589    top_exec->ExecParsed(args.borrow());
590    top_exec->WaitDone();
591    int exit_code=top_exec->ExitCode();
592 
593    top_exec->AtExit();
594    top_exec->WaitDone();
595 
596    if(Job::NumberOfJobs()>0)
597    {
598       top_exec->SetInteractive(false);
599       move_to_background();
600    }
601    else
602    {
603       top_exec->AtExitFg();
604       top_exec->AtTerminate();
605       top_exec->WaitDone();
606    }
607    top_exec->KillAll();
608    top_exec=0;
609 
610    Job::Cleanup();
611    ConnectionSlot::Cleanup();
612    SessionPool::ClearAll();
613    FileAccess::ClassCleanup();
614    ProcWait::DeleteAll();
615    IdNameCacheCleanup();
616    SignalHook::Cleanup();
617    Log::Cleanup();
618    SMTask::Cleanup();
619    return exit_code;
620 }
621