Great, that you did. I think that using forkpty and an example of calling su might be interesting for quite a lot of C / C ++ programmers.
Therefore ventured to bring a small code.
// avp 2015 link with -lutil #include <stdio.h> #include <stdlib.h> #include <err.h> #include <sysexits.h> #include <pty.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <limits.h> // returns master pty fd or -1 on error int pty_execvp (char *argv[], pid_t *child) { int master; if (!(*child = forkpty(&master, 0, 0, 0))) { execvp(argv[0], argv); /* Вот это незадача... Здесь мы в child process, а stderr и stdout (да и управляющий /dev/tty) уже перенаправлены на master. Ни о каких log-файлах нам неизвестно, так что похоже ничего не остается, как отправить error message вызвавшей нас функции через master. Ведь все равно она должна быть готова к обработке сообщений об ошибках в программе argv[0]. */ err(EX_UNAVAILABLE, "PID %ld exec %s", (long)getpid(), argv[0]); // NOT REACHABLE } else if (*child == -1) { warn("PID %ld forkpty %s", (long)getpid, argv[0]); return -1; } struct termios t; tcgetattr(master, &t); cfmakeraw(&t); tcsetattr(master, TCSAFLUSH, &t); return master; } /* Run su -l USER -c COMMAND in child process Returns pty (COMMAND's stdin, stdout, stderr) as FILE * (or NULL on error) */ FILE * run_su (const char *cmd, const char *user, const char *passwd, pid_t *child) { /* Возможно эту функцию стоит дополнить установкой таймаута... (особенно если ее модифицировать для запуска scp/ssh) */ char *ecmd, /* Для упрощения нашей задачи по определению успешности запуска /bin/su добавим к выводу CMD "уникальную" строку, которую сформируем в ok_reply[]. Для этого вместо "su -c CMD" запустим "su -c echo OK_REPLY; CMD" и если первая прочитанная от su строка (после передачи пароля) не равна ожидаемой, то будем считать, что это ошибка аутентификации. */ ok_reply[20], buf[LINE_MAX]; char *su[] = { (char *)"/bin/su", (char *)"-l", (char *)user, (char *)"-c", 0, //"echo xaxa ; ls -l /tmp", 0 }; FILE *pty; int bsize = sizeof(buf) - 1, l, master; sprintf(ok_reply, "OK %ld", (long)getpid()); if (asprintf(&ecmd, "echo %s; %s", ok_reply, cmd) < 0) { perror("run_su() asprintf"); return 0; } su[4] = ecmd; master = pty_execvp(su, child); free(ecmd); if (master < 0) return 0; /* Теоретически эти 10 байт (слово "Password: ") надо бы читать порциями в цикле и с таймаутом, проверяя не закрыл ли child свой tty */ if ((l = read(master, buf, bsize)) < 0) { perror("run_su() read Password: "); goto Err; } buf[l] = 0; if (strncmp("Password: ", buf, 10)) { fputs("Unexpected su message: ", stderr); fputs(buf, stderr); goto Err; } if (write(master, passwd, strlen(passwd)) < 0 || write(master, "\n", 1) < 0 || read(master, buf, 1) < 0) { perror("run_su() handshake su error"); goto Err; } if (!(pty = fdopen(master, "r+"))) { perror("fun_su() fdopen pty"); Err:; close(master); return 0; } if (!fgets(buf, bsize, pty) || strncmp(ok_reply, buf, strlen(ok_reply))) { fprintf(stderr, "Auth failure. Message: %s\n", buf); fclose(pty); return 0; } return pty; } int main (int ac, char *av[]) { pid_t child; FILE *pty = run_su("ls -al /tmp", "testuser", "toor", &child); int c; puts("Gooo"); while (pty && (c = fgetc(pty)) != EOF) putchar(c); if (wait(&c) != child) err(1, "unexpected child"); if (WIFEXITED(c)) printf("result: %d\n", WEXITSTATUS(c)); else printf("terminated %d\n", WTERMSIG(c)); return puts("End") == EOF; }