/* * $Id$ * * Program and URL opener * Provides startup notification, crash handler and other features * Part of Equinox Desktop Environment (EDE). * Copyright (c) 2000-2006 EDE Authors. * * This program is licenced under terms of the * GNU General Public Licence version 2 or newer. * See COPYING for details. */ #include "elauncher.h" #include "../edeconf.h" #include "../edelib2/process.h" #include "../edelib2/MimeType.h" using namespace fltk; using namespace edelib; // TODO: find where to replace magic constants with fltk::PATH_MAX // globals used in forking int fds_[3]; char *cmd_; int pid_; // command-line parameters bool param_root = false; bool param_secure = false; bool param_term = false; // from configuration file bool use_sudo = false; char *output; /*char * itoa(int value, char *string, int radix) { char tmp[33]; char *tp = tmp; int i; unsigned v; int sign; char *sp; if (radix > 36 || radix <= 1) { return 0; } sign = (radix == 10 && value < 0); if (sign) v = -value; else v = (unsigned)value; while (v || tp == tmp) { i = v % radix; v = v / radix; if (i < 10) *tp++ = i+'0'; else *tp++ = i + 'a' - 10; } if (string == 0) string = (char *)malloc((tp-tmp)+sign+1); sp = string; if (sign) *sp++ = '-'; while (tp > tmp) *sp++ = *--tp; *sp = 0; return string; }*/ // -------------------------------------------- // Show busy cursor - not working // -------------------------------------------- void show_busy_screen(bool busy) { // We can't use fltk::Cursor cause it can be set only per widget... // and only if you overload that widget! // I hate OOP :) ::Cursor xcursor; if (busy) xcursor = XCreateFontCursor(xdisplay, XC_watch); else xcursor = XCreateFontCursor(xdisplay, XC_arrow); // Hopefully this is desktop? XDefineCursor(xdisplay, CreatedWindow::first->xid, xcursor); sleep (3); } // -------------------------------------------- // Show a generic window for displaying output stream // -------------------------------------------- void output_window_close(Widget *w) { w->window()->hide(); } void output_window(char *title, char *content) { int height=0; TextBuffer buffer; buffer.text(content); for (unsigned i=0;i550) height=550; if (height<100) height=100; Window window(500, height); window.label(title); window.begin(); TextDisplay message(0, 0, 500, height-23, content); window.resizable(message); message.color(WHITE); message.textcolor(BLACK); message.buffer(buffer); Button* button; button = new ReturnButton(410, height-23, 80, 23, _("&Ok")); button->callback(output_window_close); window.hotspot(button); // window.focus(button); window.end(); window.exec(); } // -------------------------------------------- // Crash window - with details // -------------------------------------------- #define GDBPATH "/usr/bin/gdb" static xpmImage crash_pix((const char **)crash_xpm); Window *crashWindow; Button *crashDetailsButton, *crashCloseButton; TextDisplay *backTraceTD; TextBuffer *gdbbuffer; void cb_crashDetails(Widget *w) { if (backTraceTD->visible()) { backTraceTD->hide(); crashWindow->resize(450,110); } else { crashWindow->resize(450,395); backTraceTD->show(); } } void cb_crashOk(Widget *w) { w->window()->hide(); } // Execute gdb and place output into gdbbuffer bool get_me_gdb_output(int crashpid) { int pid, status; extern char **environ; status=0; pid = fork (); if (pid == -1) return false; if (pid == 0) { // child char *argv[4]; char tmp[1000]; argv[0] = "sh"; argv[1] = "-c"; argv[2] = tmp; snprintf (argv[2], 999, "echo bt>/tmp/gdbbatch; echo q>>/tmp/gdbbatch; "GDBPATH" %s --core core.%d --command /tmp/gdbbatch --quiet > /tmp/gdboutput", cmd_, crashpid); argv[3] = NULL; if (execve ("/bin/sh", argv, environ) == -1) perror ("/bin/sh"); return false; // Error } do { if (waitpid (pid, &status, 0) == -1) { if (errno != EINTR) return false; // Error } else { gdbbuffer->loadfile("/tmp/gdboutput"); // Take out the garbage char *corefile = (char*)malloc(20); snprintf (corefile, sizeof(corefile)-1, "./core.%d", crashpid); unlink(corefile); free(corefile); return true; } } while (1); } void crashmessage(char *command, int pid) { gdbbuffer = new TextBuffer; Window* w; { Window* o = crashWindow = new Window(450, 110, _("The program has crashed")); w = o; o->shortcut(0xff1b); o->begin(); { Button* o = crashDetailsButton = new Button(250, 75, 90, 25, _("&Details...")); o->callback((Callback*)cb_crashDetails); o->type(Button::TOGGLE); } { Button* o = crashCloseButton = new Button(350, 75, 90, 25, _("&Close")); o->callback((Callback*)cb_crashOk); } { InvisibleBox* o = new InvisibleBox(60, 5, 380, 16, _("An error occured in program:")); o->align(ALIGN_LEFT|ALIGN_INSIDE); } { InvisibleBox* o = new InvisibleBox(90, 20, 380, 16, command); o->labelfont(o->labelfont()->bold()); o->align(ALIGN_LEFT|ALIGN_INSIDE); } { InvisibleBox* o = new InvisibleBox(60, 35, 380, 30, _("Please inform the authors of this program and provide the details below.")); o->align(ALIGN_LEFT|ALIGN_INSIDE|ALIGN_WRAP); } { InvisibleBox* o = new InvisibleBox(15, 15, 35, 35, ""); o->image(crash_pix); } { TextDisplay* o = backTraceTD = new TextDisplay(10, 110, 430, 275, ""); o->hide(); o->color(WHITE); o->textcolor(BLACK); o->buffer(gdbbuffer); } o->end(); } w->show(); flush(); // Is there gdb on the system? struct stat *buf = (struct stat*)malloc(sizeof(struct stat)); if (stat (GDBPATH, buf) != 0 || !get_me_gdb_output(pid)) crashDetailsButton->deactivate(); w->exec(); return; } // -------------------------------------------- // Error message window // -------------------------------------------- // This should be replaced with one of redesigned standard dialogs... static xpmImage error_pix((const char **)error_xpm); void cb_errOk(Widget *w) { w->window()->hide(); } void errormessage(char *part1, char *part2, char *part3) { Window* w; { Window* o = new Window(350, 100, _("Error")); w = o; o->shortcut(0xff1b); o->begin(); { ReturnButton* o = new ReturnButton(250, 65, 90, 25, _("&OK")); o->callback((Callback*)cb_errOk); } { InvisibleBox* o = new InvisibleBox(60, 5, 280, 16, part1); o->align(ALIGN_LEFT|ALIGN_INSIDE); } { InvisibleBox* o = new InvisibleBox(90, 20, 280, 16, part2); o->labelfont(o->labelfont()->bold()); o->align(ALIGN_LEFT|ALIGN_INSIDE); } { InvisibleBox* o = new InvisibleBox(60, 35, 280, 30, part3); o->align(ALIGN_LEFT|ALIGN_INSIDE|ALIGN_WRAP); } { InvisibleBox* o = new InvisibleBox(15, 15, 35, 35, ""); o->image(error_pix); } o->end(); } w->exec(); return; } // -------------------------------------------- // Depending on exit status, show some nice dialogs // -------------------------------------------- void process_output_status(int exit_status, PtyProcess* child) { char *messages1[257], *messages2[257]; // FIXME: do we still need this init? for (int i=0;i<256;i++) { messages1[i] = ""; messages2[i] = ""; } if (exit_status == PtyProcess::Killed) exit_status = 256; messages1[127] = _("Program not found:"); messages2[127] = _("Perhaps it is not installed properly. Check your $PATH value."); // messages1[14] = _("Segmentation fault in child process:"); // messages2[14] = _(""); messages1[126] = _("File is not executable:"); messages2[126] = _("Is this really a program? If it is, you should check its permissions."); messages1[256] = _("Program was terminated:"); messages2[256] = _(""); if (exit_status == PtyProcess::Crashed) { // Nice bomb window crashmessage(cmd_,child->pid()); } else if (!(messages1[exit_status] == "")) { // we have special message for this status errormessage(messages1[exit_status],cmd_,messages2[exit_status]); } else { fprintf(stderr, _("Elauncher: child's exited normally with status %d\n"), exit_status); if (exit_status>0) { // unknown status, display stdout & stderr char *buffer; char output[65535]; bool changed=false; strcpy(output,""); while (buffer = child->readLine()) { strcat(output, buffer); changed=true; } if (changed) output_window(_("Program output"),output); } } } // -------------------------------------------- // Core function that handles su/sudo, waits for program to // finish and then calls the output handler // -------------------------------------------- // this is our internal message: #define CONTMSG "elauncher_ok_to_continue" // these are part of sudo/su chat: #define PWDQ "Password:" #define BADPWD "/bin/su: incorrect password" #define SUDOBADPWD "Sorry, try again." // We can't use const char* because of strtok later int start_child_process(char *cmd) { if (strlen(cmd)<1) return 0; // show_busy_screen(true); // return 0; // This is so that we can get a backtrace in case of crash struct rlimit *rlim = (struct rlimit*)malloc(sizeof(struct rlimit)); getrlimit (RLIMIT_CORE, rlim); rlim_t old_rlimit = rlim->rlim_cur; // keep previous rlimit rlim->rlim_cur = RLIM_INFINITY; setrlimit (RLIMIT_CORE, rlim); // Prepare array as needed by exec() char *parts[4]; if (param_root) { if (use_sudo) { parts[0] = "/bin/sudo"; parts[1] = ""; } else { parts[0] = "/bin/su"; parts[1] = "-c"; } // This "continue message" prevents accidentally exposing password int length = strlen("echo "CONTMSG)+strlen("; ")+strlen(cmd); parts[2] = (char*)malloc(length); snprintf(parts[2],length,"echo %s; %s",CONTMSG,cmd); parts[3] = NULL; } else { parts[0] = "/bin/sh"; parts[1] = "-c"; parts[2] = strdup(cmd); parts[3] = NULL; } // the actual command is this: cmd_ = strtok(cmd," "); tryagain: PtyProcess *child = new PtyProcess(); child->setEnvironment((const char**)environ); if (child->exec(parts[0], (const char**)parts) < 0) { if (ask(_("Error starting program. Try again?"))) goto tryagain; else return 0; } // Wait for process to actually start. Shouldn't last long while (1) { int p = child->pid(); if (p != 0 && child->checkPid(p)) break; int exit = child->checkPidExited(p); if (exit != -2) { // Process is DOA fprintf (stderr, "Elauncher: Process has died unexpectedly! Exit status: %d\n",exit); delete child; goto tryagain; } fprintf (stderr, "Elauncher: Not started yet...\n"); } // Run program as root using su or sudo if (param_root) { char *line; const char *pwd = password(_("This program requires administrator privileges.\nPlease enter your password below:")); if (pwd == 0) { fprintf(stderr,"Canceled\n"); exit(1); } // Chat routine while (1) { line = child->readLine(); // This covers other cases of failed process startup // Our su command should at least produce CONTMSG if (line == 0 && child->checkPidExited(child->pid()) != PtyProcess::NotExited) { // TODO: capture stdout? as in sudo error? fprintf (stderr, "Elauncher: su process has died unexpectedly in chat stage!\n"); delete child; if (choice_alert (_("Failed to start authentication. Try again"), 0, _("Yes"), _("No")) == 2) return 0; goto tryagain; } if (strncasecmp(line,PWDQ,strlen(PWDQ))== 0) child->writeLine(pwd,true); if (strncasecmp(line,CONTMSG,strlen(CONTMSG)) == 0) break; // program starts... if ((strncasecmp(line,BADPWD,strlen(BADPWD)) == 0) || (strncasecmp(line,SUDOBADPWD,strlen(SUDOBADPWD)) == 0)) { // end process child->waitForChild(); delete child; if (choice_alert (_("The password is wrong. Try again?"), 0, _("Yes"), _("No")) == 2) return 0; goto tryagain; } } } // Wait for program to end, but don't lose the output // show_busy_screen(false); // cursor(CURSOR_ARROW); int child_val = child->runChild(); process_output_status(child_val,child); // deallocate one string we mallocated free(parts[2]); delete child; // Revert old rlimit rlim->rlim_cur = old_rlimit; setrlimit (RLIMIT_CORE, rlim); return 0; } // -------------------------------------------- // Analyze command and, if it's URL, call appropriate application // Otherwise assume that it's executable and run it // (Code mostly copied over from former eRun) // -------------------------------------------- void run_resource(const char *cmd) { char pRun[256]; char browser[256]; // look up default browser in config Config pGlobalConfig(Config::find_file("ede.conf", 0)); pGlobalConfig.get("Web", "Browser", browser, 0, sizeof(browser)); if(pGlobalConfig.error() && !browser) { strncpy(browser, "mozilla", sizeof(browser)); } // We might need this later, so try to optimize file reads pGlobalConfig.get("System","UseSudo", use_sudo, false); // split cmd to protocol and location char *protocol = strdup(cmd); char *location = strchr(protocol, ':'); if (location) *(location++) = '\0'; // cut off at ':' // is cmd a proper URL? if((location) && (strchr(protocol, ' ') == NULL)) { if (strcasecmp(protocol,"file") == 0) // use mimetypes { MimeType m(location); if (m.command()) strncpy(pRun, m.command(), sizeof(pRun)-1); else { // unknown extension char m_printout[256]; snprintf(m_printout, sizeof(m_printout)-1, _("Unknown file type:\n\t%s\nTo open this file in 'appname' please use\n 'appname %s'"), location, location); alert(m_printout); return; } } // TODO: split protocols into own file else if (strcasecmp(protocol, "http")==0 || strcasecmp(protocol, "ftp")==0) { snprintf(pRun, sizeof(pRun)-1, "%s %s", browser, cmd); } // search engine urls else if (strcasecmp(protocol, "gg")==0) { snprintf(pRun, sizeof(pRun)-1, "%s http://www.google.com/search?q=\"%s\"", browser, location); } else if (strcasecmp(protocol, "leo")==0) { snprintf(pRun, sizeof(pRun)-1, "%s http://dict.leo.org/?search=\"%s\"", browser, location); } else if (strcasecmp(protocol, "av")==0) { snprintf(pRun, sizeof(pRun)-1, "%s http://www.altavista.com/sites/search/web?q=\"%s\"", browser, location); } else // Unkown URL type - let browser deal with it { snprintf(pRun, sizeof(pRun)-1, "%s %s", browser, cmd); } } else // local executable // TODO: parse the standard parameters to the executable if any exists in the *.desktop file. { if (param_secure) { char message[256]; snprintf (message, sizeof(message)-1, _("You have requested to execute program %s via Elauncher. However, secure mode was enabled. Execution has been prevented."), cmd); alert (message); exit(1); } else { snprintf(pRun, sizeof(pRun)-1, "%s", cmd); } } delete [] protocol; // Additional parameters if (param_term) { char termapp[256]; pGlobalConfig.get("Terminal", "Terminal", termapp, 0, sizeof(termapp)); char tmp[256]; snprintf (tmp, sizeof(pRun)-1, "%s -e %s",termapp,pRun); strcpy (pRun, tmp); } if (param_root) { // nothing special to do here } // continue with execution start_child_process(pRun); } // -------------------------------------------- // Draw GUI run dialog. This is shown when no parameters are given // (Code mostly copied over from former eRun) // -------------------------------------------- static xpmImage run_pix((const char **)run_xpm); Window* windowRunDialog; Input* inputRunDialog; CheckButton* runAsRoot; static void cb_OK(Button*, void*) { param_root = runAsRoot->value(); windowRunDialog->hide(); flush(); // Window will not hide without this... run_resource(inputRunDialog->value()); } static void cb_Cancel(Button*, void*) { windowRunDialog->hide(); } static void cb_Browse(Button*, void*) { const char *file_types = _("Executables (*.*), *, All files (*.*), *"); const char *fileName = file_chooser(_("File selection..."), "*.*", inputRunDialog->value()); // TODO: fix file filter when we get a new dialog if (fileName) { inputRunDialog->value(fileName); } } void run_dialog() { Window* w = windowRunDialog = new Window(350, 175, _("Open...")); w->when(WHEN_ENTER_KEY); w->begin(); { InvisibleBox* o = new InvisibleBox(5, 5, 55, 70); o->image(run_pix); o->align(ALIGN_CENTER|ALIGN_INSIDE); } { InvisibleBox* o = new InvisibleBox(60, 5, 285, 70, _("Type the location you want to open or the name of the program you want to run\ . (Possible prefixes are: http:, ftp:, gg:, av:, leo:)")); o->align(132|ALIGN_INSIDE); } { Input* o = inputRunDialog = new Input(60, 80, 180, 25, _("Open:")); o->align(132); //o->when(WHEN_ENTER_KEY); } { Button* o = new Button(250, 80, 90, 25, _("&Browse...")); o->callback((Callback*)cb_Browse); } { CheckButton* o = runAsRoot = new CheckButton(60, 110, 90, 25, _("Run as &root")); } { ReturnButton* o = new ReturnButton(150, 140, 90, 25, _("&OK")); o->callback((Callback*)cb_OK); o->shortcut(ReturnKey); // ENTER } { Button* o = new Button(250, 140, 90, 25, _("&Cancel")); o->callback((Callback*)cb_Cancel); } // -- window manager should do this // w->x(fltk::xvisual->w / 2 - (w->w()/2)); // w->y( (fltk::xvisual->h / 2) - (w->h()/2)); w->end(); w->show(); run(); } // Show console help on parameters void showHelp() { printf ("ELauncher - "); printf (_("program and URL opener for EDE.\n")); printf ("Copyright (c) 2004,2005 EDE Authors\n"); printf (_("Licenced under terms of GNU General Public Licence v2.0 or newer.\n\n")); printf (_("Usage:\n")); printf ("\telauncher [OPTIONS] [URL]\n"); printf ("\telauncher [OPTIONS] [PROGRAM]\n\n"); printf ("elauncher URL -\n"); printf (_("\tParse URL in form protocol:address and open in appropriate program.\n\tURLs with protocol 'file' are opened based on their MIME type.\n")); printf ("elauncher PROGRAM -\n"); printf (_("\tRun the program. If no path is given, look in $PATH. To give parameters\n\tto program, use quotes e.g.:\n")); printf ("\t\telauncher --term \"rm -rf /\"\n\n"); printf (_("Options:\n")); printf (" -h, --help\t- "); printf (_("This help screen.\n")); printf (" --root\t- "); printf (_("Run as root. Dialog is opened to enter password.\n")); printf (" --secure\t- "); printf (_("Prevent running programs. Only URLs are allowed.\n")); printf (" --term\t- "); printf (_("Open in default terminal app.\n\n")); } // parse command line parameters int main (int argc, char **argv) { char url[255]; url[0] = '\0'; // fl_init_locale_support("elauncher", PREFIX"/share/locale"); for (int i=1; i