static void add_cmd_block(char *cmdstr) { char *sv, *eol; cmdstr = sv = xstrdup(cmdstr); do { eol = strchr(cmdstr, '\n'); next: if (eol) { /* Count preceding slashes */ int slashes = 0; char *sl = eol; while (sl != cmdstr && *--sl == '\\') slashes++; /* Odd number of preceding slashes - newline is escaped */ if (slashes & 1) { overlapping_strcpy(eol - 1, eol); eol = strchr(eol, '\n'); goto next; } *eol = '\0'; } add_cmd(cmdstr); cmdstr = eol + 1; } while (eol); free(sv); }
/* IPv6 knows scoped address types i.e. link and site local addresses. Link * local addresses can have a scope identifier to specify the * interface/link an address is valid on (e.g. fe80::1%eth0). This scope * identifier is only valid on a single node. * * RFC 4007 says that the scope identifier MUST NOT be sent across the wire, * unless all nodes agree on the semantic. Apache e.g. regards zone identifiers * in the Host header as invalid requests, see * https://issues.apache.org/bugzilla/show_bug.cgi?id=35122 */ static void strip_ipv6_scope_id(char *host) { char *scope, *cp; /* bbox wget actually handles IPv6 addresses without [], like * wget "http://::1/xxx", but this is not standard. * To save code, _here_ we do not support it. */ if (host[0] != '[') return; /* not IPv6 */ scope = strchr(host, '%'); if (!scope) return; /* Remove the IPv6 zone identifier from the host address */ cp = strchr(host, ']'); if (!cp || (cp[1] != ':' && cp[1] != '\0')) { /* malformed address (not "[xx]:nn" or "[xx]") */ return; } /* cp points to "]...", scope points to "%eth0]..." */ overlapping_strcpy(scope, cp); }
int klogd_main(int argc UNUSED_PARAM, char **argv) { int i = 0; char *opt_c; int opt; int used = 0; opt = getopt32(argv, "c:n", &opt_c); if (opt & OPT_LEVEL) { /* Valid levels are between 1 and 8 */ i = xatou_range(opt_c, 1, 8); } if (!(opt & OPT_FOREGROUND)) { bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); } openlog("kernel", 0, LOG_KERN); bb_signals(BB_FATAL_SIGS, klogd_signal); signal(SIGHUP, SIG_IGN); /* "Open the log. Currently a NOP" */ klogctl(1, NULL, 0); /* "printk() prints a message on the console only if it has a loglevel * less than console_loglevel". Here we set console_loglevel = i. */ if (i) klogctl(8, NULL, i); syslog(LOG_NOTICE, "klogd started: %s", bb_banner); while (1) { int n; int priority; char *start; /* "2 -- Read from the log." */ start = log_buffer + used; n = klogctl(2, start, KLOGD_LOGBUF_SIZE-1 - used); if (n < 0) { if (errno == EINTR) continue; syslog(LOG_ERR, "klogd: error %d in klogctl(2): %m", errno); break; } start[n] = '\0'; /* klogctl buffer parsing modelled after code in dmesg.c */ /* Process each newline-terminated line in the buffer */ start = log_buffer; while (1) { char *newline = strchrnul(start, '\n'); if (*newline == '\0') { /* This line is incomplete... */ if (start != log_buffer) { /* move it to the front of the buffer */ overlapping_strcpy(log_buffer, start); used = newline - start; /* don't log it yet */ break; } /* ...but if buffer is full, log it anyway */ used = 0; newline = NULL; } else { *newline++ = '\0'; } /* Extract the priority */ priority = LOG_INFO; if (*start == '<') { start++; if (*start) { /* kernel never generates multi-digit prios */ priority = (*start - '0'); start++; } if (*start == '>') start++; } /* Log (only non-empty lines) */ if (*start) syslog(priority, "%s", start); if (!newline) break; start = newline; } } return EXIT_FAILURE; }
int klogd_main(int argc UNUSED_PARAM, char **argv) { int i = 0; char *opt_c; int opt; int used; unsigned int cnt; opt = getopt32(argv, "c:n", &opt_c); if (opt & OPT_LEVEL) { /* Valid levels are between 1 and 8 */ i = xatou_range(opt_c, 1, 8); } if (!(opt & OPT_FOREGROUND)) { bb_daemonize_or_rexec(DAEMON_CHDIR_ROOT, argv); } logmode = LOGMODE_SYSLOG; /* klogd_open() before openlog(), since it might use fixed fd 3, * and openlog() also may use the same fd 3 if we swap them: */ klogd_open(); openlog("kernel", 0, LOG_KERN); /* * glibc problem: for some reason, glibc changes LOG_KERN to LOG_USER * above. The logic behind this is that standard * http://pubs.opengroup.org/onlinepubs/9699919799/functions/syslog.html * says the following about openlog and syslog: * "LOG_USER * Messages generated by arbitrary processes. * This is the default facility identifier if none is specified." * * I believe glibc misinterpreted this text as "if openlog's * third parameter is 0 (=LOG_KERN), treat it as LOG_USER". * Whereas it was meant to say "if *syslog* is called with facility * 0 in its 1st parameter without prior call to openlog, then perform * implicit openlog(LOG_USER)". * * As a result of this, eh, feature, standard klogd was forced * to open-code its own openlog and syslog implementation (!). * * Note that prohibiting openlog(LOG_KERN) on libc level does not * add any security: any process can open a socket to "/dev/log" * and write a string "<0>Voila, a LOG_KERN + LOG_EMERG message" * * Google code search tells me there is no widespread use of * openlog("foo", 0, 0), thus fixing glibc won't break userspace. * * The bug against glibc was filed: * bugzilla.redhat.com/show_bug.cgi?id=547000 */ if (i) klogd_setloglevel(i); signal(SIGHUP, SIG_IGN); /* We want klogd_read to not be restarted, thus _norestart: */ bb_signals_recursive_norestart(BB_FATAL_SIGS, record_signo); syslog(LOG_NOTICE, "klogd started: %s", bb_banner); used = 0; cnt = 0; while (!bb_got_signal) { int n; int priority; char *start; char *eor; /* "2 -- Read from the log." */ start = log_buffer + used; n = klogd_read(start, KLOGD_LOGBUF_SIZE-1 - used); if (n < 0) { if (errno == EINTR) continue; bb_perror_msg(READ_ERROR); break; } start[n] = '\0'; eor = &start[n]; /* Process each newline-terminated line in the buffer */ start = log_buffer; while (1) { char *newline = strchrnul(start, '\n'); if (*newline == '\0') { /* This line is incomplete */ /* move it to the front of the buffer */ overlapping_strcpy(log_buffer, start); used = newline - start; if (used < KLOGD_LOGBUF_SIZE-1) { /* buffer isn't full */ break; } /* buffer is full, log it anyway */ used = 0; newline = NULL; } else { *newline++ = '\0'; } /* Extract the priority */ priority = LOG_INFO; if (*start == '<') { start++; if (*start) { /* kernel never generates multi-digit prios */ priority = (*start - '0'); start++; } if (*start == '>') start++; } /* Log (only non-empty lines) */ if (*start) { syslog(priority, "%s", start); /* give syslog time to catch up */ ++cnt; if ((cnt & 0x07) == 0 && (cnt < 300 || (eor - start) > 200)) usleep(50 * 1000); } if (!newline) break; start = newline; } } klogd_close(); syslog(LOG_NOTICE, "klogd: exiting"); if (bb_got_signal) kill_myself_with_sig(bb_got_signal); return EXIT_FAILURE; }
static void process_files(void) { char *pattern_space, *next_line; int linenum = 0; char last_puts_char = '\n'; char last_gets_char, next_gets_char; sed_cmd_t *sed_cmd; int substituted; /* Prime the pump */ next_line = get_next_line(&next_gets_char, &last_puts_char); /* Go through every line in each file */ again: substituted = 0; /* Advance to next line. Stop if out of lines. */ pattern_space = next_line; if (!pattern_space) return; last_gets_char = next_gets_char; /* Read one line in advance so we can act on the last line, * the '$' address */ next_line = get_next_line(&next_gets_char, &last_puts_char); linenum++; /* For every line, go through all the commands */ restart: for (sed_cmd = G.sed_cmd_head; sed_cmd; sed_cmd = sed_cmd->next) { int old_matched, matched; old_matched = sed_cmd->in_match; /* Determine if this command matches this line: */ dbg("match1:%d", sed_cmd->in_match); dbg("match2:%d", (!sed_cmd->beg_line && !sed_cmd->end_line && !sed_cmd->beg_match && !sed_cmd->end_match)); dbg("match3:%d", (sed_cmd->beg_line > 0 && (sed_cmd->end_line || sed_cmd->end_match ? (sed_cmd->beg_line <= linenum) : (sed_cmd->beg_line == linenum) ) )); dbg("match4:%d", (beg_match(sed_cmd, pattern_space))); dbg("match5:%d", (sed_cmd->beg_line == -1 && next_line == NULL)); /* Are we continuing a previous multi-line match? */ sed_cmd->in_match = sed_cmd->in_match /* Or is no range necessary? */ || (!sed_cmd->beg_line && !sed_cmd->end_line && !sed_cmd->beg_match && !sed_cmd->end_match) /* Or did we match the start of a numerical range? */ || (sed_cmd->beg_line > 0 && (sed_cmd->end_line || sed_cmd->end_match /* note: even if end is numeric and is < linenum too, * GNU sed matches! We match too, therefore we don't * check here that linenum <= end. * Example: * printf '1\n2\n3\n4\n' | sed -n '1{N;N;d};1p;2,3p;3p;4p' * first three input lines are deleted; * 4th line is matched and printed * by "2,3" (!) and by "4" ranges */ ? (sed_cmd->beg_line <= linenum) /* N,end */ : (sed_cmd->beg_line == linenum) /* N */ ) ) /* Or does this line match our begin address regex? */ || (beg_match(sed_cmd, pattern_space)) /* Or did we match last line of input? */ || (sed_cmd->beg_line == -1 && next_line == NULL); /* Snapshot the value */ matched = sed_cmd->in_match; dbg("cmd:'%c' matched:%d beg_line:%d end_line:%d linenum:%d", sed_cmd->cmd, matched, sed_cmd->beg_line, sed_cmd->end_line, linenum); /* Is this line the end of the current match? */ if (matched) { if (sed_cmd->end_line <= -2) { /* address2 is +N, i.e. N lines from beg_line */ sed_cmd->end_line = linenum + (-sed_cmd->end_line - 2); } /* once matched, "n,xxx" range is dead, disabling it */ if (sed_cmd->beg_line > 0) { sed_cmd->beg_line = -2; } dbg("end1:%d", sed_cmd->end_line ? sed_cmd->end_line == -1 ? !next_line : (sed_cmd->end_line <= linenum) : !sed_cmd->end_match); dbg("end2:%d", sed_cmd->end_match && old_matched && !regexec(sed_cmd->end_match,pattern_space, 0, NULL, 0)); sed_cmd->in_match = !( /* has the ending line come, or is this a single address command? */ (sed_cmd->end_line ? sed_cmd->end_line == -1 ? !next_line : (sed_cmd->end_line <= linenum) : !sed_cmd->end_match ) /* or does this line matches our last address regex */ || (sed_cmd->end_match && old_matched && (regexec(sed_cmd->end_match, pattern_space, 0, NULL, 0) == 0) ) ); } /* Skip blocks of commands we didn't match */ if (sed_cmd->cmd == '{') { if (sed_cmd->invert ? matched : !matched) { unsigned nest_cnt = 0; while (1) { if (sed_cmd->cmd == '{') nest_cnt++; if (sed_cmd->cmd == '}') { nest_cnt--; if (nest_cnt == 0) break; } sed_cmd = sed_cmd->next; if (!sed_cmd) bb_error_msg_and_die("unterminated {"); } } continue; } /* Okay, so did this line match? */ if (sed_cmd->invert ? matched : !matched) continue; /* no */ /* Update last used regex in case a blank substitute BRE is found */ if (sed_cmd->beg_match) { G.previous_regex_ptr = sed_cmd->beg_match; } /* actual sedding */ dbg("pattern_space:'%s' next_line:'%s' cmd:%c", pattern_space, next_line, sed_cmd->cmd); switch (sed_cmd->cmd) { /* Print line number */ case '=': fprintf(G.nonstdout, "%d\n", linenum); break; /* Write the current pattern space up to the first newline */ case 'P': { char *tmp = strchr(pattern_space, '\n'); if (tmp) { *tmp = '\0'; /* TODO: explain why '\n' below */ sed_puts(pattern_space, '\n'); *tmp = '\n'; break; } /* Fall Through */ } /* Write the current pattern space to output */ case 'p': /* NB: we print this _before_ the last line * (of current file) is printed. Even if * that line is nonterminated, we print * '\n' here (gnu sed does the same) */ sed_puts(pattern_space, '\n'); break; /* Delete up through first newline */ case 'D': { char *tmp = strchr(pattern_space, '\n'); if (tmp) { overlapping_strcpy(pattern_space, tmp + 1); goto restart; } } /* discard this line. */ case 'd': goto discard_line; /* Substitute with regex */ case 's': if (!do_subst_command(sed_cmd, &pattern_space)) break; dbg("do_subst_command succeeded:'%s'", pattern_space); substituted |= 1; /* handle p option */ if (sed_cmd->sub_p) sed_puts(pattern_space, last_gets_char); /* handle w option */ if (sed_cmd->sw_file) puts_maybe_newline( pattern_space, sed_cmd->sw_file, &sed_cmd->sw_last_char, last_gets_char); break; /* Append line to linked list to be printed later */ case 'a': append(xstrdup(sed_cmd->string)); break; /* Insert text before this line */ case 'i': sed_puts(sed_cmd->string, '\n'); break; /* Cut and paste text (replace) */ case 'c': /* Only triggers on last line of a matching range. */ if (!sed_cmd->in_match) sed_puts(sed_cmd->string, '\n'); goto discard_line; /* Read file, append contents to output */ case 'r': { FILE *rfile; rfile = fopen_for_read(sed_cmd->string); if (rfile) { char *line; while ((line = xmalloc_fgetline(rfile)) != NULL) append(line); fclose(rfile); } break; } /* Write pattern space to file. */ case 'w': puts_maybe_newline( pattern_space, sed_cmd->sw_file, &sed_cmd->sw_last_char, last_gets_char); break; /* Read next line from input */ case 'n': if (!G.be_quiet) sed_puts(pattern_space, last_gets_char); if (next_line == NULL) { /* If no next line, jump to end of script and exit. */ goto discard_line; } free(pattern_space); pattern_space = next_line; last_gets_char = next_gets_char; next_line = get_next_line(&next_gets_char, &last_puts_char); substituted = 0; linenum++; break; /* Quit. End of script, end of input. */ case 'q': /* Exit the outer while loop */ free(next_line); next_line = NULL; goto discard_commands; /* Append the next line to the current line */ case 'N': { int len; /* If no next line, jump to end of script and exit. */ /* http://www.gnu.org/software/sed/manual/sed.html: * "Most versions of sed exit without printing anything * when the N command is issued on the last line of * a file. GNU sed prints pattern space before exiting * unless of course the -n command switch has been * specified. This choice is by design." */ if (next_line == NULL) { //goto discard_line; goto discard_commands; /* GNU behavior */ } /* Append next_line, read new next_line. */ len = strlen(pattern_space); pattern_space = xrealloc(pattern_space, len + strlen(next_line) + 2); pattern_space[len] = '\n'; strcpy(pattern_space + len+1, next_line); last_gets_char = next_gets_char; next_line = get_next_line(&next_gets_char, &last_puts_char); linenum++; break; } /* Test/branch if substitution occurred */ case 't': if (!substituted) break; substituted = 0; /* Fall through */ /* Test/branch if substitution didn't occur */ case 'T': if (substituted) break; /* Fall through */ /* Branch to label */ case 'b': if (!sed_cmd->string) goto discard_commands; else sed_cmd = branch_to(sed_cmd->string); break; /* Transliterate characters */ case 'y': { int i, j; for (i = 0; pattern_space[i]; i++) { for (j = 0; sed_cmd->string[j]; j += 2) { if (pattern_space[i] == sed_cmd->string[j]) { pattern_space[i] = sed_cmd->string[j + 1]; break; } } } break; } case 'g': /* Replace pattern space with hold space */ free(pattern_space); pattern_space = xstrdup(G.hold_space ? G.hold_space : ""); break; case 'G': /* Append newline and hold space to pattern space */ { int pattern_space_size = 2; int hold_space_size = 0; if (pattern_space) pattern_space_size += strlen(pattern_space); if (G.hold_space) hold_space_size = strlen(G.hold_space); pattern_space = xrealloc(pattern_space, pattern_space_size + hold_space_size); if (pattern_space_size == 2) pattern_space[0] = 0; strcat(pattern_space, "\n"); if (G.hold_space) strcat(pattern_space, G.hold_space); last_gets_char = '\n'; break; } case 'h': /* Replace hold space with pattern space */ free(G.hold_space); G.hold_space = xstrdup(pattern_space); break; case 'H': /* Append newline and pattern space to hold space */ { int hold_space_size = 2; int pattern_space_size = 0; if (G.hold_space) hold_space_size += strlen(G.hold_space); if (pattern_space) pattern_space_size = strlen(pattern_space); G.hold_space = xrealloc(G.hold_space, hold_space_size + pattern_space_size); if (hold_space_size == 2) *G.hold_space = 0; strcat(G.hold_space, "\n"); if (pattern_space) strcat(G.hold_space, pattern_space); break; } case 'x': /* Exchange hold and pattern space */ { char *tmp = pattern_space; pattern_space = G.hold_space ? G.hold_space : xzalloc(1); last_gets_char = '\n'; G.hold_space = tmp; break; } } /* switch */ } /* for each cmd */ /* * Exit point from sedding... */ discard_commands: /* we will print the line unless we were told to be quiet ('-n') or if the line was suppressed (ala 'd'elete) */ if (!G.be_quiet) sed_puts(pattern_space, last_gets_char); /* Delete and such jump here. */ discard_line: flush_append(&last_puts_char /*,last_gets_char*/); free(pattern_space); goto again; }
/* Called after "Backtrace:" line was read. * Example (yes, stray newline before 'B' is real): [ 86985.879]<space> Backtrace: [ 86985.880] 0: /usr/bin/Xorg (xorg_backtrace+0x2f) [0x462d8f] [ 86985.880] 1: /usr/bin/Xorg (0x400000+0x67b56) [0x467b56] [ 86985.880] 2: /lib64/libpthread.so.0 (0x30a5800000+0xf4f0) [0x30a580f4f0] [ 86985.880] 3: /usr/lib64/xorg/modules/extensions/librecord.so (0x7ff6c225e000+0x26c3) [0x7ff6c22606c3] [ 86985.880] 4: /usr/bin/Xorg (_CallCallbacks+0x3c) [0x43820c] [ 86985.880] 5: /usr/bin/Xorg (WriteToClient+0x1f5) [0x466315] [ 86985.880] 6: /usr/lib64/xorg/modules/extensions/libdri2.so (ProcDRI2WaitMSCReply+0x4f) [0x7ff6c1e4feef] [ 86985.880] 7: /usr/lib64/xorg/modules/extensions/libdri2.so (DRI2WaitMSCComplete+0x52) [0x7ff6c1e4e6d2] [ 86985.880] 8: /usr/lib64/xorg/modules/drivers/intel_drv.so (0x7ff6c1bfb000+0x25ae4) [0x7ff6c1c20ae4] [ 86985.880] 9: /usr/lib64/libdrm.so.2 (drmHandleEvent+0xa3) [0x376b407513] [ 86985.880] 10: /usr/bin/Xorg (WakeupHandler+0x6b) [0x4379db] [ 86985.880] 11: /usr/bin/Xorg (WaitForSomething+0x1a9) [0x460289] [ 86985.880] 12: /usr/bin/Xorg (0x400000+0x3379a) [0x43379a] [ 86985.880] 13: /usr/bin/Xorg (0x400000+0x22dc5) [0x422dc5] [ 86985.880] 14: /lib64/libc.so.6 (__libc_start_main+0xed) [0x30a542169d] [ 86985.880] 15: /usr/bin/Xorg (0x400000+0x230b1) [0x4230b1] [ 86985.880] Segmentation fault at address 0x7ff6bf09e010 */ static void process_xorg_bt(void) { char *reason = NULL; char *exe = NULL; GList *list = NULL; unsigned cnt = 0; char *line; while ((line = xmalloc_fgetline(stdin)) != NULL) { char *p = skip_pfx(line); /* xorg-server-1.12.0/os/osinit.c: * if (sip->si_code == SI_USER) { * ErrorF("Recieved signal %d sent by process %ld, uid %ld\n", * ^^^^^^^^ yes, typo here! Can't grep for this word! :( * signo, (long) sip->si_pid, (long) sip->si_uid); * } else { * switch (signo) { * case SIGSEGV: * case SIGBUS: * case SIGILL: * case SIGFPE: * ErrorF("%s at address %p\n", strsignal(signo), sip->si_addr); */ if (*p < '0' || *p > '9') { if (strstr(p, " at address ") || strstr(p, " sent by process ")) { overlapping_strcpy(line, p); reason = line; line = NULL; } /* TODO: Other cases when we have useful reason string? */ break; } errno = 0; char *end; IGNORE_RESULT(strtoul(p, &end, 10)); if (errno || end == p || *end != ':') break; /* This looks like bt line */ /* Guess Xorg server's executable name from it */ if (!exe) { char *filename = skip_whitespace(end + 1); char *filename_end = skip_non_whitespace(filename); char sv = *filename_end; *filename_end = '\0'; /* Does it look like "[/usr]/[s]bin/Xfoo"? */ if (strstr(filename, "bin/X")) exe = xstrdup(filename); *filename_end = sv; } /* Save it to list */ overlapping_strcpy(line, p); list = g_list_prepend(list, line); line = NULL; if (++cnt > 255) /* prevent ridiculously large bts */ break; } free(line); if (list) { list = g_list_reverse(list); char *bt = list2lines(list); /* frees list */ if (g_opts & OPT_o) printf("%s%s%s\n", bt, reason ? reason : "", reason ? "\n" : ""); if (g_opts & (OPT_d|OPT_D)) if (g_bt_count <= MAX_DUMPED_DD_COUNT) save_bt_to_dump_dir(bt, exe, reason ? reason : "Xorg server crashed"); free(bt); } free(reason); free(exe); }
int lpqr_main(int argc UNUSED_PARAM, char *argv[]) { enum { OPT_P = 1 << 0, // -P queue[@host[:port]]. If no -P is given use $PRINTER, then "lp@localhost:515" OPT_U = 1 << 1, // -U username LPR_V = 1 << 2, // -V: be verbose LPR_h = 1 << 3, // -h: want banner printed LPR_C = 1 << 4, // -C class: job "class" (? supposedly printed on banner) LPR_J = 1 << 5, // -J title: the job title for the banner page LPR_m = 1 << 6, // -m: send mail back to user LPQ_SHORT_FMT = 1 << 2, // -s: short listing format LPQ_DELETE = 1 << 3, // -d: delete job(s) LPQ_FORCE = 1 << 4, // -f: force waiting job(s) to be printed }; char tempfile[sizeof("/tmp/lprXXXXXX")]; const char *job_title; const char *printer_class = ""; // printer class, max 32 char const char *queue; // name of printer queue const char *server = "localhost"; // server[:port] of printer queue char *hostname; // N.B. IMHO getenv("USER") can be way easily spoofed! const char *user = xuid2uname(getuid()); unsigned job; unsigned opts; int fd; queue = getenv("PRINTER"); if (!queue) queue = "lp"; // parse options // TODO: set opt_complementary: s,d,f are mutually exclusive opts = getopt32(argv, (/*lp*/'r' == applet_name[2]) ? "P:U:VhC:J:m" : "P:U:sdf" , &queue, &user , &printer_class, &job_title ); argv += optind; { // queue name is to the left of '@' char *s = strchr(queue, '@'); if (s) { // server name is to the right of '@' *s = '\0'; server = s + 1; } } // do connect fd = create_and_connect_stream_or_die(server, 515); // // LPQ ------------------------ // if (/*lp*/'q' == applet_name[2]) { char cmd; // force printing of every job still in queue if (opts & LPQ_FORCE) { cmd = 1; goto command; // delete job(s) } else if (opts & LPQ_DELETE) { fdprintf(fd, "\x5" "%s %s", queue, user); while (*argv) { fdprintf(fd, " %s", *argv++); } bb_putchar('\n'); // dump current jobs status // N.B. periodical polling should be achieved // via "watch -n delay lpq" // They say it's the UNIX-way :) } else { cmd = (opts & LPQ_SHORT_FMT) ? 3 : 4; command: fdprintf(fd, "%c" "%s\n", cmd, queue); bb_copyfd_eof(fd, STDOUT_FILENO); } return EXIT_SUCCESS; } // // LPR ------------------------ // if (opts & LPR_V) bb_error_msg("connected to server"); job = getpid() % 1000; hostname = safe_gethostname(); // no files given on command line? -> use stdin if (!*argv) *--argv = (char *)"-"; fdprintf(fd, "\x2" "%s\n", queue); get_response_or_say_and_die(fd, "setting queue"); // process files do { unsigned cflen; int dfd; struct stat st; char *c; char *remote_filename; char *controlfile; // if data file is stdin, we need to dump it first if (LONE_DASH(*argv)) { strcpy(tempfile, "/tmp/lprXXXXXX"); dfd = xmkstemp(tempfile); bb_copyfd_eof(STDIN_FILENO, dfd); xlseek(dfd, 0, SEEK_SET); *argv = (char*)bb_msg_standard_input; } else { dfd = xopen(*argv, O_RDONLY); } st.st_size = 0; /* paranoia: fstat may theoretically fail */ fstat(dfd, &st); /* Apparently, some servers are buggy and won't accept 0-sized jobs. * Standard lpr works around it by refusing to send such jobs: */ if (st.st_size == 0) { bb_error_msg("nothing to print"); continue; } /* "The name ... should start with ASCII "cfA", * followed by a three digit job number, followed * by the host name which has constructed the file." * We supply 'c' or 'd' as needed for control/data file. */ remote_filename = xasprintf("fA%03u%s", job, hostname); // create control file // TODO: all lines but 2 last are constants! How we can use this fact? controlfile = xasprintf( "H" "%.32s\n" "P" "%.32s\n" /* H HOST, P USER */ "C" "%.32s\n" /* C CLASS - printed on banner page (if L cmd is also given) */ "J" "%.99s\n" /* J JOBNAME */ /* "class name for banner page and job name * for banner page commands must precede L command" */ "L" "%.32s\n" /* L USER - print banner page, with given user's name */ "M" "%.32s\n" /* M WHOM_TO_MAIL */ "l" "d%.31s\n" /* l DATA_FILE_NAME ("dfAxxx") */ , hostname, user , printer_class /* can be "" */ , ((opts & LPR_J) ? job_title : *argv) , (opts & LPR_h) ? user : "" , (opts & LPR_m) ? user : "" , remote_filename ); // delete possible "\nX\n" (that is, one-char) patterns c = controlfile; while ((c = strchr(c, '\n')) != NULL) { if (c[1] && c[2] == '\n') { overlapping_strcpy(c, c+2); } else { c++; } } // send control file if (opts & LPR_V) bb_error_msg("sending control file"); /* "Acknowledgement processing must occur as usual * after the command is sent." */ cflen = (unsigned)strlen(controlfile); fdprintf(fd, "\x2" "%u c%s\n", cflen, remote_filename); get_response_or_say_and_die(fd, "sending control file"); /* "Once all of the contents have * been delivered, an octet of zero bits is sent as * an indication that the file being sent is complete. * A second level of acknowledgement processing * must occur at this point." */ full_write(fd, controlfile, cflen + 1); /* writes NUL byte too */ get_response_or_say_and_die(fd, "sending control file"); // send data file, with name "dfaXXX" if (opts & LPR_V) bb_error_msg("sending data file"); fdprintf(fd, "\x3" "%"FILESIZE_FMT"u d%s\n", st.st_size, remote_filename); get_response_or_say_and_die(fd, "sending data file"); if (bb_copyfd_size(dfd, fd, st.st_size) != st.st_size) { // We're screwed. We sent less bytes than we advertised. bb_error_msg_and_die("local file changed size?!"); } write(fd, "", 1); // send ACK get_response_or_say_and_die(fd, "sending data file"); // delete temporary file if we dumped stdin if (*argv == (char*)bb_msg_standard_input) unlink(tempfile); // cleanup close(fd); free(remote_filename); free(controlfile); // say job accepted if (opts & LPR_V) bb_error_msg("job accepted"); // next, please! job = (job + 1) % 1000; } while (*++argv); return EXIT_SUCCESS; }
/* 1) add a user: update_passwd(FILE, USER, REMAINING_PWLINE, NULL) only if CONFIG_ADDUSER=y and applet_name[0] == 'a' like in adduser 2) add a group: update_passwd(FILE, GROUP, REMAINING_GRLINE, NULL) only if CONFIG_ADDGROUP=y and applet_name[0] == 'a' like in addgroup 3) add a user to a group: update_passwd(FILE, GROUP, NULL, MEMBER) only if CONFIG_FEATURE_ADDUSER_TO_GROUP=y, applet_name[0] == 'a' like in addgroup and member != NULL 4) delete a user: update_passwd(FILE, USER, NULL, NULL) 5) delete a group: update_passwd(FILE, GROUP, NULL, NULL) 6) delete a user from a group: update_passwd(FILE, GROUP, NULL, MEMBER) only if CONFIG_FEATURE_DEL_USER_FROM_GROUP=y and member != NULL 7) change user's password: update_passwd(FILE, USER, NEW_PASSWD, NULL) only if CONFIG_PASSWD=y and applet_name[0] == 'p' like in passwd or if CONFIG_CHPASSWD=y and applet_name[0] == 'c' like in chpasswd 8) delete a user from all groups: update_passwd(FILE, NULL, NULL, MEMBER) This function does not validate the arguments fed to it so the calling program should take care of that. Returns number of lines changed, or -1 on error. */ int FAST_FUNC update_passwd(const char *filename, const char *name, const char *new_passwd, const char *member) { #if !(ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP) #define member NULL #endif struct stat sb; struct flock lock; FILE *old_fp; FILE *new_fp; char *fnamesfx; char *sfx_char; char *name_colon; int old_fd; int new_fd; int i; int changed_lines; int ret = -1; /* failure */ /* used as a bool: "are we modifying /etc/shadow?" */ #if ENABLE_FEATURE_SHADOWPASSWDS const char *shadow = strstr(filename, "shadow"); #else # define shadow NULL #endif filename = xmalloc_follow_symlinks(filename); if (filename == NULL) return ret; if (name) check_selinux_update_passwd(name); /* New passwd file, "/etc/passwd+" for now */ fnamesfx = xasprintf("%s+", filename); sfx_char = &fnamesfx[strlen(fnamesfx)-1]; name_colon = xasprintf("%s:", name ? name : ""); if (shadow) old_fp = fopen(filename, "r+"); else old_fp = fopen_or_warn(filename, "r+"); if (!old_fp) { if (shadow) ret = 0; /* missing shadow is not an error */ goto free_mem; } old_fd = fileno(old_fp); selinux_preserve_fcontext(old_fd); /* Try to create "/etc/passwd+". Wait if it exists. */ i = 30; do { // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC? new_fd = open(fnamesfx, O_WRONLY|O_CREAT|O_EXCL, 0600); if (new_fd >= 0) goto created; if (errno != EEXIST) break; usleep(100000); /* 0.1 sec */ } while (--i); bb_perror_msg("can't create '%s'", fnamesfx); goto close_old_fp; created: if (fstat(old_fd, &sb) == 0) { fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */ fchown(new_fd, sb.st_uid, sb.st_gid); } errno = 0; new_fp = xfdopen_for_write(new_fd); /* Backup file is "/etc/passwd-" */ *sfx_char = '-'; /* Delete old backup */ i = (unlink(fnamesfx) && errno != ENOENT); /* Create backup as a hardlink to current */ if (i || link(filename, fnamesfx)) bb_perror_msg("warning: can't create backup copy '%s'", fnamesfx); *sfx_char = '+'; /* Lock the password file before updating */ lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; if (fcntl(old_fd, F_SETLK, &lock) < 0) bb_perror_msg("warning: can't lock '%s'", filename); lock.l_type = F_UNLCK; /* Read current password file, write updated /etc/passwd+ */ changed_lines = 0; while (1) { char *cp, *line; line = xmalloc_fgetline(old_fp); if (!line) /* EOF/error */ break; if (!name && member) { /* Delete member from all groups */ /* line is "GROUP:PASSWD:[member1[,member2]...]" */ unsigned member_len = strlen(member); char *list = strrchr(line, ':'); while (list) { list++; next_list_element: if (is_prefixed_with(list, member)) { char c; changed_lines++; c = list[member_len]; if (c == '\0') { if (list[-1] == ',') list--; *list = '\0'; break; } if (c == ',') { overlapping_strcpy(list, list + member_len + 1); goto next_list_element; } changed_lines--; } list = strchr(list, ','); } fprintf(new_fp, "%s\n", line); goto next; } cp = is_prefixed_with(line, name_colon); if (!cp) { fprintf(new_fp, "%s\n", line); goto next; } /* We have a match with "name:"... */ /* cp points past "name:" */ #if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP if (member) { /* It's actually /etc/group+, not /etc/passwd+ */ if (ENABLE_FEATURE_ADDUSER_TO_GROUP && applet_name[0] == 'a' ) { /* Add user to group */ fprintf(new_fp, "%s%s%s\n", line, last_char_is(line, ':') ? "" : ",", member); changed_lines++; } else if (ENABLE_FEATURE_DEL_USER_FROM_GROUP /* && applet_name[0] == 'd' */ ) { /* Delete user from group */ char *tmp; const char *fmt = "%s"; /* find the start of the member list: last ':' */ cp = strrchr(line, ':'); /* cut it */ *cp++ = '\0'; /* write the cut line name:passwd:gid: * or name:!:: */ fprintf(new_fp, "%s:", line); /* parse the tokens of the member list */ tmp = cp; while ((cp = strsep(&tmp, ",")) != NULL) { if (strcmp(member, cp) != 0) { fprintf(new_fp, fmt, cp); fmt = ",%s"; } else { /* found member, skip it */ changed_lines++; } } fprintf(new_fp, "\n"); } } else #endif if ((ENABLE_PASSWD && applet_name[0] == 'p') || (ENABLE_CHPASSWD && applet_name[0] == 'c') ) { /* Change passwd */ cp = strchrnul(cp, ':'); /* move past old passwd */ if (shadow && *cp == ':') { /* /etc/shadow's field 3 (passwd change date) needs updating */ /* move past old change date */ cp = strchrnul(cp + 1, ':'); /* "name:" + "new_passwd" + ":" + "change date" + ":rest of line" */ fprintf(new_fp, "%s%s:%u%s\n", name_colon, new_passwd, (unsigned)(time(NULL)) / (24*60*60), cp); } else { /* "name:" + "new_passwd" + ":rest of line" */ fprintf(new_fp, "%s%s%s\n", name_colon, new_passwd, cp); } changed_lines++; } /* else delete user or group: skip the line */ next: free(line); } if (changed_lines == 0) { #if ENABLE_FEATURE_ADDUSER_TO_GROUP || ENABLE_FEATURE_DEL_USER_FROM_GROUP if (member) { if (ENABLE_ADDGROUP && applet_name[0] == 'a') bb_error_msg("can't find %s in %s", name, filename); if (ENABLE_DELGROUP && applet_name[0] == 'd') bb_error_msg("can't find %s in %s", member, filename); } #endif if ((ENABLE_ADDUSER || ENABLE_ADDGROUP) && applet_name[0] == 'a' && !member ) { /* add user or group */ fprintf(new_fp, "%s%s\n", name_colon, new_passwd); changed_lines++; } } fcntl(old_fd, F_SETLK, &lock); /* We do want all of them to execute, thus | instead of || */ errno = 0; if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp)) || rename(fnamesfx, filename) ) { /* At least one of those failed */ bb_perror_nomsg(); goto unlink_new; } /* Success: ret >= 0 */ ret = changed_lines; unlink_new: if (ret < 0) unlink(fnamesfx); close_old_fp: fclose(old_fp); free_mem: free(fnamesfx); free((char *)filename); free(name_colon); return ret; }