static llist_t *get_block_backed_filesystems(void) { static const char filesystems[2][sizeof("/proc/filesystems")] = { "/etc/filesystems", "/proc/filesystems", }; char *fs, *buf; llist_t *list = 0; int i; FILE *f; for (i = 0; i < 2; i++) { f = fopen(filesystems[i], "r"); if (!f) continue; while ((buf = xmalloc_getline(f)) != 0) { if (!strncmp(buf, "nodev", 5) && isspace(buf[5])) continue; fs = skip_whitespace(buf); if (*fs=='#' || *fs=='*' || !*fs) continue; llist_add_to_end(&list, xstrdup(fs)); free(buf); } if (ENABLE_FEATURE_CLEAN_UP) fclose(f); } return list; }
static llist_t *get_block_backed_filesystems(void) { char *fs, *buf, *filesystems[] = {"/etc/filesystems", "/proc/filesystems", 0}; llist_t *list = 0; int i; FILE *f; for(i = 0; filesystems[i]; i++) { if(!(f = fopen(filesystems[i], "r"))) continue; for(fs = buf = 0; (fs = buf = bb_get_chomped_line_from_file(f)); free(buf)) { if(!strncmp(buf,"nodev",5) && isspace(buf[5])) continue; while(isspace(*fs)) fs++; if(*fs=='#' || *fs=='*') continue; if(!*fs) continue; llist_add_to_end(&list,bb_xstrdup(fs)); } if (ENABLE_FEATURE_CLEAN_UP) fclose(f); } return list; }
int ar_main(int argc UNUSED_PARAM, char **argv) { archive_handle_t *archive_handle; unsigned opt, t; archive_handle = init_handle(); /* --: prepend '-' to the first argument if required */ /* -1: at least one param is reqd */ /* one of p,t,x[,r] is required */ opt_complementary = "--:-1:p:t:x"IF_FEATURE_AR_CREATE(":r"); opt = getopt32(argv, "voc""ptx"IF_FEATURE_AR_CREATE("r")); argv += optind; t = opt / FIRST_CMD; if (t & (t-1)) /* more than one of p,t,x[,r] are specified */ bb_show_usage(); if (opt & AR_CMD_PRINT) { archive_handle->action_data = data_extract_to_stdout; } if (opt & AR_CMD_LIST) { archive_handle->action_header = header_list; } if (opt & AR_CMD_EXTRACT) { archive_handle->action_data = data_extract_all; } if (opt & AR_OPT_PRESERVE_DATE) { archive_handle->ah_flags |= ARCHIVE_RESTORE_DATE; } if (opt & AR_OPT_VERBOSE) { archive_handle->action_header = header_verbose_list_ar; } #if ENABLE_FEATURE_AR_CREATE archive_handle->ar__name = *argv; #endif archive_handle->src_fd = xopen(*argv++, (opt & AR_CMD_INSERT) ? O_RDWR | O_CREAT : O_RDONLY ); if (*argv) archive_handle->filter = filter_accept_list; while (*argv) { llist_add_to_end(&archive_handle->accept, *argv++); } #if ENABLE_FEATURE_AR_CREATE if (opt & AR_CMD_INSERT) return write_ar_archive(archive_handle); #endif unpack_ar_archive(archive_handle); return EXIT_SUCCESS; }
int FAST_FUNC string_to_llist(char *string, llist_t **llist, const char *delim) { char *tok; int len = 0; while ((tok = strsep(&string, delim)) != NULL) { if (tok[0] == '\0') continue; llist_add_to_end(llist, xstrdup(tok)); len += strlen(tok); } return len; }
static void del_line_matching(const char *login, const char *filename) { char *line; FILE *passwd; int len = strlen(login); int found = 0; llist_t *plist = NULL; passwd = fopen_or_warn(filename, "r"); if (!passwd) return; while ((line = xmalloc_fgets(passwd))) { if (!strncmp(line, login, len) && line[len] == ':' ) { found++; free(line); } else { llist_add_to_end(&plist, line); } } if (!ENABLE_FEATURE_CLEAN_UP) { if (!found) { bb_error_msg("can't find '%s' in '%s'", login, filename); return; } passwd = fopen_or_warn(filename, "w"); if (passwd) while ((line = llist_pop(&plist))) fputs(line, passwd); } else { if (!found) { bb_error_msg("can't find '%s' in '%s'", login, filename); goto clean_up; } fclose(passwd); passwd = fopen_or_warn(filename, "w"); if (passwd) { clean_up: while ((line = llist_pop(&plist))) { if (found) fputs(line, passwd); free(line); } fclose(passwd); } } }
static void add_user_to_group(char **args, const char *path, FILE *(*fopen_func)(const char *fileName, const char *mode)) { char *line; int len = strlen(args[1]); llist_t *plist = NULL; FILE *group_file; group_file = fopen_func(path, "r"); if (!group_file) return; while ((line = xmalloc_fgetline(group_file)) != NULL) { /* Find the group */ if (!strncmp(line, args[1], len) && line[len] == ':' ) { /* Add the new user */ line = xasprintf("%s%s%s", line, last_char_is(line, ':') ? "" : ",", args[0]); } llist_add_to_end(&plist, line); } if (ENABLE_FEATURE_CLEAN_UP) { fclose(group_file); group_file = fopen_func(path, "w"); while ((line = llist_pop(&plist))) { if (group_file) fprintf(group_file, "%s\n", line); free(line); } if (group_file) fclose(group_file); } else { group_file = fopen_func(path, "w"); if (group_file) while ((line = llist_pop(&plist))) fprintf(group_file, "%s\n", line); } }
static void add_probe(const char *name) { struct module_entry *m; m = get_or_add_modentry(name); if (!(option_mask32 & MODPROBE_OPT_REMOVE) && (m->flags & MODULE_FLAG_LOADED) ) { DBG("skipping %s, it is already loaded", name); return; } m->probed_name = name; m->flags |= MODULE_FLAG_NEED_DEPS; llist_add_to_end(&G.probes, m); G.num_unresolved_deps++; if (ENABLE_FEATURE_MODUTILS_SYMBOLS && strncmp(m->modname, "symbol:", 7) == 0 ) { G.need_symbols = 1; } }
static void add_probe(const char *name) { struct module_entry *m; m = get_or_add_modentry(name); if (!(option_mask32 & (OPT_REMOVE | OPT_SHOW_DEPS)) && (m->flags & (MODULE_FLAG_LOADED | MODULE_FLAG_BUILTIN)) ) { DBG("skipping %s, it is already loaded", name); return; } DBG("queuing %s", name); m->probed_name = name; m->flags |= MODULE_FLAG_NEED_DEPS; llist_add_to_end(&G.probes, m); G.num_unresolved_deps++; if (ENABLE_FEATURE_MODUTILS_SYMBOLS && is_prefixed_with(m->modname, "symbol:") ) { G.need_symbols = 1; } }
int sendmail_main(int argc UNUSED_PARAM, char **argv) { char *opt_connect = opt_connect; char *opt_from = NULL; char *s; llist_t *list = NULL; char *host = sane_address(safe_gethostname()); unsigned nheaders = 0; int code; enum { HDR_OTHER = 0, HDR_TOCC, HDR_BCC, } last_hdr = 0; int check_hdr; int has_to = 0; enum { //--- standard options OPT_t = 1 << 0, // read message for recipients, append them to those on cmdline OPT_f = 1 << 1, // sender address OPT_o = 1 << 2, // various options. -oi IMPLIED! others are IGNORED! OPT_i = 1 << 3, // IMPLIED! //--- BB specific options OPT_w = 1 << 4, // network timeout OPT_H = 1 << 5, // use external connection helper OPT_S = 1 << 6, // specify connection string OPT_a = 1 << 7, // authentication tokens OPT_v = 1 << 8, // verbosity }; // init global variables INIT_G(); // save initial stdin since body is piped! xdup2(STDIN_FILENO, 3); G.fp0 = xfdopen_for_read(3); // parse options // -v is a counter, -H and -S are mutually exclusive, -a is a list opt_complementary = "vv:w+:H--S:S--H:a::"; // N.B. since -H and -S are mutually exclusive they do not interfere in opt_connect // -a is for ssmtp (http://downloads.openwrt.org/people/nico/man/man8/ssmtp.8.html) compatibility, // it is still under development. opts = getopt32(argv, "tf:o:iw:H:S:a::v", &opt_from, NULL, &timeout, &opt_connect, &opt_connect, &list, &verbose); //argc -= optind; argv += optind; // process -a[upm]<token> options if ((opts & OPT_a) && !list) bb_show_usage(); while (list) { char *a = (char *) llist_pop(&list); if ('u' == a[0]) G.user = xstrdup(a+1); if ('p' == a[0]) G.pass = xstrdup(a+1); // N.B. we support only AUTH LOGIN so far //if ('m' == a[0]) // G.method = xstrdup(a+1); } // N.B. list == NULL here //bb_info_msg("OPT[%x] AU[%s], AP[%s], AM[%s], ARGV[%s]", opts, au, ap, am, *argv); // connect to server // connection helper ordered? -> if (opts & OPT_H) { const char *args[] = { "sh", "-c", opt_connect, NULL }; // plug it in launch_helper(args); // Now: // our stdout will go to helper's stdin, // helper's stdout will be available on our stdin. // Wait for initial server message. // If helper (such as openssl) invokes STARTTLS, the initial 220 // is swallowed by helper (and not repeated after TLS is initiated). // We will send NOOP cmd to server and check the response. // We should get 220+250 on plain connection, 250 on STARTTLSed session. // // The problem here is some servers delay initial 220 message, // and consider client to be a spammer if it starts sending cmds // before 220 reached it. The code below is unsafe in this regard: // in non-STARTTLSed case, we potentially send NOOP before 220 // is sent by server. // Ideas? (--delay SECS opt? --assume-starttls-helper opt?) code = smtp_check("NOOP", -1); if (code == 220) // we got 220 - this is not STARTTLSed connection, // eat 250 response to our NOOP smtp_check(NULL, 250); else if (code != 250) bb_error_msg_and_die("SMTP init failed"); } else { // vanilla connection int fd; // host[:port] not explicitly specified? -> use $SMTPHOST // no $SMTPHOST? -> use localhost if (!(opts & OPT_S)) { opt_connect = getenv("SMTPHOST"); if (!opt_connect) opt_connect = (char *)"127.0.0.1"; } // do connect fd = create_and_connect_stream_or_die(opt_connect, 25); // and make ourselves a simple IO filter xmove_fd(fd, STDIN_FILENO); xdup2(STDIN_FILENO, STDOUT_FILENO); // Wait for initial server 220 message smtp_check(NULL, 220); } // we should start with modern EHLO if (250 != smtp_checkp("EHLO %s", host, -1)) smtp_checkp("HELO %s", host, 250); // perform authentication if (opts & OPT_a) { smtp_check("AUTH LOGIN", 334); // we must read credentials unless they are given via -a[up] options if (!G.user || !G.pass) get_cred_or_die(4); encode_base64(NULL, G.user, NULL); smtp_check("", 334); encode_base64(NULL, G.pass, NULL); smtp_check("", 235); } // set sender // N.B. we have here a very loosely defined algorythm // since sendmail historically offers no means to specify secrets on cmdline. // 1) server can require no authentication -> // we must just provide a (possibly fake) reply address. // 2) server can require AUTH -> // we must provide valid username and password along with a (possibly fake) reply address. // For the sake of security username and password are to be read either from console or from a secured file. // Since reading from console may defeat usability, the solution is either to read from a predefined // file descriptor (e.g. 4), or again from a secured file. // got no sender address? use auth name, then UID username as a last resort if (!opt_from) { opt_from = xasprintf("%s@%s", G.user ? G.user : xuid2uname(getuid()), xgethostbyname(host)->h_name); } free(host); smtp_checkp("MAIL FROM:<%s>", opt_from, 250); // process message // read recipients from message and add them to those given on cmdline. // this means we scan stdin for To:, Cc:, Bcc: lines until an empty line // and then use the rest of stdin as message body code = 0; // set "analyze headers" mode while ((s = xmalloc_fgetline(G.fp0)) != NULL) { dump: // put message lines doubling leading dots if (code) { // escape leading dots // N.B. this feature is implied even if no -i (-oi) switch given // N.B. we need to escape the leading dot regardless of // whether it is single or not character on the line if ('.' == s[0] /*&& '\0' == s[1] */) bb_putchar('.'); // dump read line send_r_n(s); free(s); continue; } // analyze headers // To: or Cc: headers add recipients check_hdr = (0 == strncasecmp("To:", s, 3)); has_to |= check_hdr; if (opts & OPT_t) { if (check_hdr || 0 == strncasecmp("Bcc:" + 1, s, 3)) { rcptto_list(s+3); last_hdr = HDR_TOCC; goto addheader; } // Bcc: header adds blind copy (hidden) recipient if (0 == strncasecmp("Bcc:", s, 4)) { rcptto_list(s+4); free(s); last_hdr = HDR_BCC; continue; // N.B. Bcc: vanishes from headers! } } check_hdr = (list && isspace(s[0])); if (strchr(s, ':') || check_hdr) { // other headers go verbatim // N.B. RFC2822 2.2.3 "Long Header Fields" allows for headers to occupy several lines. // Continuation is denoted by prefixing additional lines with whitespace(s). // Thanks (stefan.seyfried at googlemail.com) for pointing this out. if (check_hdr && last_hdr != HDR_OTHER) { rcptto_list(s+1); if (last_hdr == HDR_BCC) continue; // N.B. Bcc: vanishes from headers! } else { last_hdr = HDR_OTHER; } addheader: // N.B. we allow MAX_HEADERS generic headers at most to prevent attacks if (MAX_HEADERS && ++nheaders >= MAX_HEADERS) goto bail; llist_add_to_end(&list, s); } else { // a line without ":" (an empty line too, by definition) doesn't look like a valid header // so stop "analyze headers" mode reenter: // put recipients specified on cmdline check_hdr = 1; while (*argv) { char *t = sane_address(*argv); rcptto(t); //if (MAX_HEADERS && ++nheaders >= MAX_HEADERS) // goto bail; if (!has_to) { const char *hdr; if (check_hdr && argv[1]) hdr = "To: %s,"; else if (check_hdr) hdr = "To: %s"; else if (argv[1]) hdr = "To: %s," + 3; else hdr = "To: %s" + 3; llist_add_to_end(&list, xasprintf(hdr, t)); check_hdr = 0; } argv++; } // enter "put message" mode // N.B. DATA fails iff no recipients were accepted (or even provided) // in this case just bail out gracefully if (354 != smtp_check("DATA", -1)) goto bail; // dump the headers while (list) { send_r_n((char *) llist_pop(&list)); } // stop analyzing headers code++; // N.B. !s means: we read nothing, and nothing to be read in the future. // just dump empty line and break the loop if (!s) { send_r_n(""); break; } // go dump message body // N.B. "s" already contains the first non-header line, so pretend we read it from input goto dump; } } // odd case: we didn't stop "analyze headers" mode -> message body is empty. Reenter the loop // N.B. after reenter code will be > 0 if (!code) goto reenter; // finalize the message smtp_check(".", 250); bail: // ... and say goodbye smtp_check("QUIT", 221); // cleanup if (ENABLE_FEATURE_CLEAN_UP) fclose(G.fp0); return EXIT_SUCCESS; }
int tar_main(int argc UNUSED_PARAM, char **argv) { char FAST_FUNC (*get_header_ptr)(archive_handle_t *) = get_header_tar; archive_handle_t *tar_handle; char *base_dir = NULL; const char *tar_filename = "-"; unsigned opt; int verboseFlag = 0; #if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM llist_t *excludes = NULL; #endif /* Initialise default values */ tar_handle = init_handle(); tar_handle->ah_flags = ARCHIVE_CREATE_LEADING_DIRS | ARCHIVE_PRESERVE_DATE | ARCHIVE_EXTRACT_UNCONDITIONAL; /* Apparently only root's tar preserves perms (see bug 3844) */ if (getuid() != 0) tar_handle->ah_flags |= ARCHIVE_NOPRESERVE_PERM; /* Prepend '-' to the first argument if required */ opt_complementary = "--:" // first arg is options "tt:vv:" // count -t,-v "?:" // bail out with usage instead of error return "X::T::" // cumulative lists #if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM "\xff::" // cumulative lists for --exclude #endif IF_FEATURE_TAR_CREATE("c:") "t:x:" // at least one of these is reqd IF_FEATURE_TAR_CREATE("c--tx:t--cx:x--ct") // mutually exclusive IF_NOT_FEATURE_TAR_CREATE("t--x:x--t"); // mutually exclusive #if ENABLE_FEATURE_TAR_LONG_OPTIONS applet_long_options = tar_longopts; #endif opt = getopt32(argv, "txC:f:Opvk" IF_FEATURE_TAR_CREATE( "ch" ) IF_FEATURE_SEAMLESS_BZ2( "j" ) IF_FEATURE_SEAMLESS_LZMA("a" ) IF_FEATURE_TAR_FROM( "T:X:") IF_FEATURE_SEAMLESS_GZ( "z" ) IF_FEATURE_SEAMLESS_Z( "Z" ) , &base_dir // -C dir , &tar_filename // -f filename IF_FEATURE_TAR_FROM(, &(tar_handle->accept)) // T IF_FEATURE_TAR_FROM(, &(tar_handle->reject)) // X #if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM , &excludes // --exclude #endif , &verboseFlag // combined count for -t and -v , &verboseFlag // combined count for -t and -v ); argv += optind; if (verboseFlag) tar_handle->action_header = header_verbose_list; if (verboseFlag == 1) tar_handle->action_header = header_list; if (opt & OPT_EXTRACT) tar_handle->action_data = data_extract_all; if (opt & OPT_2STDOUT) tar_handle->action_data = data_extract_to_stdout; if (opt & OPT_KEEP_OLD) tar_handle->ah_flags &= ~ARCHIVE_EXTRACT_UNCONDITIONAL; if (opt & OPT_NOPRESERVE_OWN) tar_handle->ah_flags |= ARCHIVE_NOPRESERVE_OWN; if (opt & OPT_NOPRESERVE_PERM) tar_handle->ah_flags |= ARCHIVE_NOPRESERVE_PERM; if (opt & OPT_NUMERIC_OWNER) tar_handle->ah_flags |= ARCHIVE_NUMERIC_OWNER; if (opt & OPT_GZIP) get_header_ptr = get_header_tar_gz; if (opt & OPT_BZIP2) get_header_ptr = get_header_tar_bz2; if (opt & OPT_LZMA) get_header_ptr = get_header_tar_lzma; if (opt & OPT_COMPRESS) get_header_ptr = get_header_tar_Z; #if ENABLE_FEATURE_TAR_FROM tar_handle->reject = append_file_list_to_list(tar_handle->reject); #if ENABLE_FEATURE_TAR_LONG_OPTIONS /* Append excludes to reject */ while (excludes) { llist_t *next = excludes->link; excludes->link = tar_handle->reject; tar_handle->reject = excludes; excludes = next; } #endif tar_handle->accept = append_file_list_to_list(tar_handle->accept); #endif /* Setup an array of filenames to work with */ /* TODO: This is the same as in ar, separate function ? */ while (*argv) { /* kill trailing '/' unless the string is just "/" */ char *cp = last_char_is(*argv, '/'); if (cp > *argv) *cp = '\0'; llist_add_to_end(&tar_handle->accept, *argv); argv++; } if (tar_handle->accept || tar_handle->reject) tar_handle->filter = filter_accept_reject_list; /* Open the tar file */ { FILE *tar_stream; int flags; if (opt & OPT_CREATE) { /* Make sure there is at least one file to tar up. */ if (tar_handle->accept == NULL) bb_error_msg_and_die("empty archive"); tar_stream = stdout; /* Mimicking GNU tar 1.15.1: */ flags = O_WRONLY | O_CREAT | O_TRUNC; } else { tar_stream = stdin; flags = O_RDONLY; } if (LONE_DASH(tar_filename)) { tar_handle->src_fd = fileno(tar_stream); tar_handle->seek = seek_by_read; } else { if (ENABLE_FEATURE_TAR_AUTODETECT && flags == O_RDONLY) { get_header_ptr = get_header_tar; tar_handle->src_fd = open_zipped(tar_filename); if (tar_handle->src_fd < 0) bb_perror_msg_and_die("can't open '%s'", tar_filename); } else { tar_handle->src_fd = xopen(tar_filename, flags); } } } if (base_dir) xchdir(base_dir); #ifdef CHECK_FOR_CHILD_EXITCODE /* We need to know whether child (gzip/bzip/etc) exits abnormally */ signal(SIGCHLD, handle_SIGCHLD); #endif /* create an archive */ if (opt & OPT_CREATE) { #if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 int zipMode = 0; if (ENABLE_FEATURE_SEAMLESS_GZ && (opt & OPT_GZIP)) zipMode = 1; if (ENABLE_FEATURE_SEAMLESS_BZ2 && (opt & OPT_BZIP2)) zipMode = 2; #endif /* NB: writeTarFile() closes tar_handle->src_fd */ return writeTarFile(tar_handle->src_fd, verboseFlag, opt & OPT_DEREFERENCE, tar_handle->accept, tar_handle->reject, zipMode); } while (get_header_ptr(tar_handle) == EXIT_SUCCESS) continue; /* Check that every file that should have been extracted was */ while (tar_handle->accept) { if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) && !find_list_entry(tar_handle->passed, tar_handle->accept->data) ) { bb_error_msg_and_die("%s: not found in archive", tar_handle->accept->data); } tar_handle->accept = tar_handle->accept->link; } if (ENABLE_FEATURE_CLEAN_UP /* && tar_handle->src_fd != STDIN_FILENO */) close(tar_handle->src_fd); return EXIT_SUCCESS; }
int chat_main(int argc UNUSED_PARAM, char **argv) { int record_fd = -1; bool echo = 0; // collection of device replies which cause unconditional termination llist_t *aborts = NULL; // inactivity period int timeout = DEFAULT_CHAT_TIMEOUT; // maximum length of abort string #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN size_t max_abort_len = 0; #else #define max_abort_len MAX_ABORT_LEN #endif #if ENABLE_FEATURE_CHAT_TTY_HIFI struct termios tio0, tio; #endif // directive names enum { DIR_HANGUP = 0, DIR_ABORT, #if ENABLE_FEATURE_CHAT_CLR_ABORT DIR_CLR_ABORT, #endif DIR_TIMEOUT, DIR_ECHO, DIR_SAY, DIR_RECORD, }; // make x* functions fail with correct exitcode xfunc_error_retval = ERR_IO; // trap vanilla signals to prevent process from being killed suddenly bb_signals(0 + (1 << SIGHUP) + (1 << SIGINT) + (1 << SIGTERM) + (1 << SIGPIPE) , signal_handler); #if ENABLE_FEATURE_CHAT_TTY_HIFI tcgetattr(STDIN_FILENO, &tio); tio0 = tio; cfmakeraw(&tio); tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio); #endif #if ENABLE_FEATURE_CHAT_SWALLOW_OPTS getopt32(argv, "vVsSE"); argv += optind; #else argv++; // goto first arg #endif // handle chat expect-send pairs while (*argv) { // directive given? process it int key = index_in_strings( "HANGUP\0" "ABORT\0" #if ENABLE_FEATURE_CHAT_CLR_ABORT "CLR_ABORT\0" #endif "TIMEOUT\0" "ECHO\0" "SAY\0" "RECORD\0" , *argv ); if (key >= 0) { // cache directive value char *arg = *++argv; // OFF -> 0, anything else -> 1 bool onoff = (0 != strcmp("OFF", arg)); // process directive if (DIR_HANGUP == key) { // turn SIGHUP on/off signal(SIGHUP, onoff ? signal_handler : SIG_IGN); } else if (DIR_ABORT == key) { // append the string to abort conditions #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN size_t len = strlen(arg); if (len > max_abort_len) max_abort_len = len; #endif llist_add_to_end(&aborts, arg); #if ENABLE_FEATURE_CHAT_CLR_ABORT } else if (DIR_CLR_ABORT == key) { // remove the string from abort conditions // N.B. gotta refresh maximum length too... #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN max_abort_len = 0; #endif for (llist_t *l = aborts; l; l = l->link) { #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN size_t len = strlen(l->data); #endif if (!strcmp(arg, l->data)) { llist_unlink(&aborts, l); continue; } #if ENABLE_FEATURE_CHAT_VAR_ABORT_LEN if (len > max_abort_len) max_abort_len = len; #endif } #endif } else if (DIR_TIMEOUT == key) { // set new timeout // -1 means OFF timeout = atoi(arg) * 1000; // 0 means default // >0 means value in msecs if (!timeout) timeout = DEFAULT_CHAT_TIMEOUT; } else if (DIR_ECHO == key) { // turn echo on/off // N.B. echo means dumping device input/output to stderr echo = onoff; } else if (DIR_RECORD == key) { // turn record on/off // N.B. record means dumping device input to a file // close previous record_fd if (record_fd > 0) close(record_fd); // N.B. do we have to die here on open error? record_fd = (onoff) ? xopen(arg, O_WRONLY|O_CREAT|O_TRUNC) : -1; } else if (DIR_SAY == key) { // just print argument verbatim // TODO: should we use full_write() to avoid unistd/stdio conflict? bb_error_msg("%s", arg); } // next, please! argv++; // ordinary expect-send pair! } else { //----------------------- // do expect //----------------------- int expect_len; size_t buf_len = 0; size_t max_len = max_abort_len; struct pollfd pfd; #if ENABLE_FEATURE_CHAT_NOFAIL int nofail = 0; #endif char *expect = *argv++; // sanity check: shall we really expect something? if (!expect) goto expect_done; #if ENABLE_FEATURE_CHAT_NOFAIL // if expect starts with - if ('-' == *expect) { // swallow - expect++; // and enter nofail mode nofail++; } #endif #ifdef ___TEST___BUF___ // test behaviour with a small buffer # undef COMMON_BUFSIZE # define COMMON_BUFSIZE 6 #endif // expand escape sequences in expect expect_len = unescape(expect, &expect_len /*dummy*/); if (expect_len > max_len) max_len = expect_len; // sanity check: // we should expect more than nothing but not more than input buffer // TODO: later we'll get rid of fixed-size buffer if (!expect_len) goto expect_done; if (max_len >= COMMON_BUFSIZE) { exitcode = ERR_MEM; goto expect_done; } // get reply pfd.fd = STDIN_FILENO; pfd.events = POLLIN; while (!exitcode && poll(&pfd, 1, timeout) > 0 && (pfd.revents & POLLIN) ) { #define buf bb_common_bufsiz1 llist_t *l; ssize_t delta; // read next char from device if (safe_read(STDIN_FILENO, buf+buf_len, 1) > 0) { // dump device input if RECORD fname if (record_fd > 0) { full_write(record_fd, buf+buf_len, 1); } // dump device input if ECHO ON if (echo > 0) { // if (buf[buf_len] < ' ') { // full_write(STDERR_FILENO, "^", 1); // buf[buf_len] += '@'; // } full_write(STDERR_FILENO, buf+buf_len, 1); } buf_len++; // move input frame if we've reached higher bound if (buf_len > COMMON_BUFSIZE) { memmove(buf, buf+buf_len-max_len, max_len); buf_len = max_len; } } // N.B. rule of thumb: values being looked for can // be found only at the end of input buffer // this allows to get rid of strstr() and memmem() // TODO: make expect and abort strings processed uniformly // abort condition is met? -> bail out for (l = aborts, exitcode = ERR_ABORT; l; l = l->link, ++exitcode) { size_t len = strlen(l->data); delta = buf_len-len; if (delta >= 0 && !memcmp(buf+delta, l->data, len)) goto expect_done; } exitcode = ERR_OK; // expected reply received? -> goto next command delta = buf_len - expect_len; if (delta >= 0 && !memcmp(buf+delta, expect, expect_len)) goto expect_done; #undef buf } /* while (have data) */ // device timed out or unexpected reply received exitcode = ERR_TIMEOUT; expect_done: #if ENABLE_FEATURE_CHAT_NOFAIL // on success and when in nofail mode // we should skip following subsend-subexpect pairs if (nofail) { if (!exitcode) { // find last send before non-dashed expect while (*argv && argv[1] && '-' == argv[1][0]) argv += 2; // skip the pair // N.B. do we really need this?! if (!*argv++ || !*argv++) break; } // nofail mode also clears all but IO errors (or signals) if (ERR_IO != exitcode) exitcode = ERR_OK; } #endif // bail out unless we expected successfully if (exitcode) break; //----------------------- // do send //----------------------- if (*argv) { #if ENABLE_FEATURE_CHAT_IMPLICIT_CR int nocr = 0; // inhibit terminating command with \r #endif char *loaded = NULL; // loaded command size_t len; char *buf = *argv++; // if command starts with @ // load "real" command from file named after @ if ('@' == *buf) { // skip the @ and any following white-space trim(++buf); buf = loaded = xmalloc_xopen_read_close(buf, NULL); } // expand escape sequences in command len = unescape(buf, &nocr); // send command alarm(timeout); pfd.fd = STDOUT_FILENO; pfd.events = POLLOUT; while (len && !exitcode && poll(&pfd, 1, -1) > 0 && (pfd.revents & POLLOUT) ) { #if ENABLE_FEATURE_CHAT_SEND_ESCAPES // "\\d" means 1 sec delay, "\\p" means 0.01 sec delay // "\\K" means send BREAK char c = *buf; if ('\\' == c) { c = *++buf; if ('d' == c) { sleep(1); len--; continue; } if ('p' == c) { usleep(10000); len--; continue; } if ('K' == c) { tcsendbreak(STDOUT_FILENO, 0); len--; continue; } buf--; } if (safe_write(STDOUT_FILENO, buf, 1) != 1) break; len--; buf++; #else len -= full_write(STDOUT_FILENO, buf, len); #endif } /* while (can write) */ alarm(0); // report I/O error if there still exists at least one non-sent char if (len) exitcode = ERR_IO; // free loaded command (if any) if (loaded) free(loaded); #if ENABLE_FEATURE_CHAT_IMPLICIT_CR // or terminate command with \r (if not inhibited) else if (!nocr) xwrite(STDOUT_FILENO, "\r", 1); #endif // bail out unless we sent command successfully if (exitcode) break; } /* if (*argv) */ } } /* while (*argv) */ #if ENABLE_FEATURE_CHAT_TTY_HIFI tcsetattr(STDIN_FILENO, TCSAFLUSH, &tio0); #endif return exitcode; }
static void append(char *s) { llist_add_to_end(&G.append_head, s); }
static void append(char *s) { llist_add_to_end(&G.append_head, xstrdup(s)); }
int sendmail_main(int argc UNUSED_PARAM, char **argv) { char *opt_connect = opt_connect; char *opt_from; char *s; llist_t *list = NULL; char *domain = sane_address(safe_getdomainname()); int code; enum { //--- standard options OPT_t = 1 << 0, // read message for recipients, append them to those on cmdline OPT_f = 1 << 1, // sender address OPT_o = 1 << 2, // various options. -oi IMPLIED! others are IGNORED! //--- BB specific options OPT_w = 1 << 3, // network timeout OPT_H = 1 << 4, // use external connection helper OPT_S = 1 << 5, // specify connection string OPT_a = 1 << 6, // authentication tokens }; // init global variables INIT_G(); // save initial stdin since body is piped! xdup2(STDIN_FILENO, 3); G.fp0 = fdopen(3, "r"); // parse options // -f is required. -H and -S are mutually exclusive opt_complementary = "f:w+:H--S:S--H:a::"; // N.B. since -H and -S are mutually exclusive they do not interfere in opt_connect // -a is for ssmtp (http://downloads.openwrt.org/people/nico/man/man8/ssmtp.8.html) compatibility, // it is still under development. opts = getopt32(argv, "tf:o:w:H:S:a::", &opt_from, NULL, &timeout, &opt_connect, &opt_connect, &list); //argc -= optind; argv += optind; // process -a[upm]<token> options if ((opts & OPT_a) && !list) bb_show_usage(); while (list) { char *a = (char *) llist_pop(&list); if ('u' == a[0]) G.user = xstrdup(a+1); if ('p' == a[0]) G.pass = xstrdup(a+1); // N.B. we support only AUTH LOGIN so far //if ('m' == a[0]) // G.method = xstrdup(a+1); } // N.B. list == NULL here //bb_info_msg("OPT[%x] AU[%s], AP[%s], AM[%s], ARGV[%s]", opts, au, ap, am, *argv); // connect to server // connection helper ordered? -> if (opts & OPT_H) { const char *args[] = { "sh", "-c", opt_connect, NULL }; // plug it in launch_helper(args); // vanilla connection } else { int fd; // host[:port] not explicitly specified? -> use $SMTPHOST // no $SMTPHOST ? -> use localhost if (!(opts & OPT_S)) { opt_connect = getenv("SMTPHOST"); if (!opt_connect) opt_connect = (char *)"127.0.0.1"; } // do connect fd = create_and_connect_stream_or_die(opt_connect, 25); // and make ourselves a simple IO filter xmove_fd(fd, STDIN_FILENO); xdup2(STDIN_FILENO, STDOUT_FILENO); } // N.B. from now we know nothing about network :) // wait for initial server OK // N.B. if we used openssl the initial 220 answer is already swallowed during openssl TLS init procedure // so we need to kick the server to see whether we are ok code = smtp_check("NOOP", -1); // 220 on plain connection, 250 on openssl-helped TLS session if (220 == code) smtp_check(NULL, 250); // reread the code to stay in sync else if (250 != code) bb_error_msg_and_die("INIT failed"); // we should start with modern EHLO if (250 != smtp_checkp("EHLO %s", domain, -1)) { smtp_checkp("HELO %s", domain, 250); } if (ENABLE_FEATURE_CLEAN_UP) free(domain); // perform authentication if (opts & OPT_a) { smtp_check("AUTH LOGIN", 334); // we must read credentials unless they are given via -a[up] options if (!G.user || !G.pass) get_cred_or_die(4); encode_base64(NULL, G.user, NULL); smtp_check("", 334); encode_base64(NULL, G.pass, NULL); smtp_check("", 235); } // set sender // N.B. we have here a very loosely defined algotythm // since sendmail historically offers no means to specify secrets on cmdline. // 1) server can require no authentication -> // we must just provide a (possibly fake) reply address. // 2) server can require AUTH -> // we must provide valid username and password along with a (possibly fake) reply address. // For the sake of security username and password are to be read either from console or from a secured file. // Since reading from console may defeat usability, the solution is either to read from a predefined // file descriptor (e.g. 4), or again from a secured file. // got no sender address? -> use system username as a resort // N.B. we marked -f as required option! //if (!G.user) { // // N.B. IMHO getenv("USER") can be way easily spoofed! // G.user = xuid2uname(getuid()); // opt_from = xasprintf("%s@%s", G.user, domain); //} //if (ENABLE_FEATURE_CLEAN_UP) // free(domain); smtp_checkp("MAIL FROM:<%s>", opt_from, 250); // process message // read recipients from message and add them to those given on cmdline. // this means we scan stdin for To:, Cc:, Bcc: lines until an empty line // and then use the rest of stdin as message body code = 0; // set "analyze headers" mode while ((s = xmalloc_fgetline(G.fp0)) != NULL) { // put message lines doubling leading dots if (code) { // escape leading dots // N.B. this feature is implied even if no -i (-oi) switch given // N.B. we need to escape the leading dot regardless of // whether it is single or not character on the line if ('.' == s[0] /*&& '\0' == s[1] */) printf("."); // dump read line printf("%s\r\n", s); free(s); continue; } // analyze headers // To: or Cc: headers add recipients if (0 == strncasecmp("To: ", s, 4) || 0 == strncasecmp("Bcc: " + 1, s, 4)) { rcptto(sane_address(s+4)); // goto addh; llist_add_to_end(&list, s); // Bcc: header adds blind copy (hidden) recipient } else if (0 == strncasecmp("Bcc: ", s, 5)) { rcptto(sane_address(s+5)); free(s); // N.B. Bcc: vanishes from headers! // other headers go verbatim } else if (s[0]) { // addh: llist_add_to_end(&list, s); // the empty line stops analyzing headers } else { free(s); // put recipients specified on cmdline while (*argv) { s = sane_address(*argv); rcptto(s); llist_add_to_end(&list, xasprintf("To: %s", s)); argv++; } // enter "put message" mode smtp_check("DATA", 354); // dump the headers while (list) { printf("%s\r\n", (char *) llist_pop(&list)); } printf("%s\r\n" + 2); // quirk for format string to be reused // stop analyzing headers code++; } } // finalize the message smtp_check(".", 250); // ... and say goodbye smtp_check("QUIT", 221); // cleanup if (ENABLE_FEATURE_CLEAN_UP) fclose(G.fp0); return EXIT_SUCCESS; }