Thread: piping password to veracrypt (Linux)

  1. #1
    Registered User
    Join Date
    Jul 2019
    Posts
    6

    Question piping password to veracrypt (Linux)

    Hello!

    To simplify mounting multiple veracrypt volumes with the same password, I wrote a little C program, which prompts for the password and then pipes it to multiple veracrypt instances. The program works but I'd like to get some technical advice from this community to ensure that the program works as intended and to make a few improvements:

    1. I wonder, whether the entered password remains anywhere in the storage after the execution of the program is finished (this is the whole point of this code, because otherwise I could have used just a shell script).

    2. In the current state the password is read and passed to veracrypt, which is executed in the background. This was not my intention, since I don't have any control about the success of the operation nor about it's duration. I tried waiting (int status; waitpid(pid, &status, 0); ) (line 72) for the first forked process (which forks into the others), in order to continue the execution until the volumes are mounted. But for some reason the piping the password stopped working correctly then (without any error message).

    3. When [2] is solved, I'd like to condition the further excecution/output on the the return value of the veracrypt process. How do I get this value?

    Code:
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>
    #include <stdbool.h>
    
    
    #define READ_END 0   
    #define WRITE_END 1 
    
    
    int main(int argc, char* argv[])
    {
        if(argc > 1 && argc%2==1)
        {
            int inst = (argc-1)/2;
            int     fd[inst][2];
            pid_t   pid;
            char    scmd[] = "veracrypt";
    
    
            for(int i = 0; i<inst;i++){
                pipe(fd[i]);
            }
            pid = fork();
    
    
            if(pid==0)
            {
                    freopen("/dev/null", "w", stdout);
                    int j=0;
    
    
                    dup2(fd[0][READ_END], STDIN_FILENO);
                    close(fd[0][WRITE_END]);
                    close(fd[0][READ_END]);
    
    
                    bool b = true;
                    for(int i=0;i<inst-1 && b;i++){
                        pid = fork();
                        if(pid==0){
                            j = i+1;
                            dup2(fd[j][READ_END], STDIN_FILENO);
                            close(fd[j][WRITE_END]);
                            close(fd[j][READ_END]);
                        }
                        else{
                                j = i;
                                b = false;
                        }
                    }
    
    
                    execlp(scmd, scmd, "-t","--protect-hidden=no","--keyfiles=","--pim=123",argv[j*2+1],argv[j*2+2],(char*) NULL);
                    fprintf(stderr, "Failed to execute '%s'\n", scmd);
                    exit(1);
            }
            else
            { 
                    char *buffer = getpass("Veracrypt Password:");
    
    
                    for(int i=0;i<inst;i++)
                    {
                        close(fd[i][READ_END]);
                        write(fd[i][WRITE_END], buffer, (strlen(buffer)+1));
                    }
                    for(int i=0;i<inst;i++){close(fd[i][WRITE_END]);}
    
    
                    srand(time(0));
                    for(int i = 0; i<strlen(buffer); i++) buffer[i] = rand();
    
    
                    free(buffer);
            }
        }
        return(0);
    }
    Thank you very much,
    oneone
    Last edited by oneone; 07-13-2019 at 04:25 AM.

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    I'm wondering what line 41 is doing
    > for(int i=0;i<inst-1 && b;i++)

    And the subsequent value of j when you eventually get to
    > execlp(scmd, scmd, "-t","--protect-hidden=no","--keyfiles=","--pim=123",argv[j*2+1],argv[j*2+2],(char*) NULL);

    For instance, there are a whole number of fork() calls which basically do nothing, because the child exits immediately.

    > 1. I wonder, whether the entered password remains anywhere in the storage after the execution of the program is finished
    > (this is the whole point of this code, because otherwise I could have used just a shell script).
    No more or less than if you'd done it as a shell script.

    Sure, yes, technically, destroying the password sooner is better.
    But destroy and then exit is barely different to just exit (as a shell-script would).

    If an attacker is already on your machine with the ability to read memory of any arbitrary process, you've already lost the game.
    The much easier option for your attacker is to simply strace the process and observe the data being passed in read() and write() calls.

    Code:
    $ cat bar.c
    #define _BSD_SOURCE
    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
      char *p = getpass("Show me");
      printf("You entered %s\n", p);
    }
    $ strace -e trace=read,write ./a.out 
    read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
    write(3, "Show me", 7Show me)                  = 7
    read(3, "hello\n", 4096)                = 6
    write(3, "\n", 1
    )                       = 1
    write(1, "You entered hello\n", 18You entered hello
    )     = 18
    +++ exited with 0 +++

    > char *buffer = getpass("Veracrypt Password:");
    SYNOPSIS
    #include <unistd.h>

    char *getpass(const char *prompt);

    Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

    getpass():
    Since glibc 2.2.2:
    _BSD_SOURCE ||
    (_XOPEN_SOURCE >= 500 ||
    _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) &&
    !(_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600)
    Before glibc 2.2.2:
    none

    DESCRIPTION
    This function is obsolete. Do not use it.

    RETURN VALUE
    The function getpass() returns a pointer to a static buffer containing (the first PASS_MAX bytes of) the password without the trailing newline, terminated by a null byte ('\0'). This buffer may be
    overwritten by a following call. On error, the terminal state is restored, errno is set appropriately, and NULL is returned.
    1. It's obsolete.
    2. It's a static buffer, so calling free() on it is wrong.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    Jul 2019
    Posts
    6
    Thank you very much for your reply!

    Quote Originally Posted by Salem View Post
    I'm wondering what line 41 is doing
    > for(int i=0;i<inst-1 && b;i++)

    And the subsequent value of j when you eventually get to
    > execlp(scmd, scmd, "-t","--protect-hidden=no","--keyfiles=","--pim=123",argv[j*2+1],argv[j*2+2],(char*) NULL);

    For instance, there are a whole number of fork() calls which basically do nothing, because the child exits immediately.
    The program is called by
    Code:
    ./myprogram container1 mountpoint1 container2 mountpoint2 ...
    Each container is mounted by a seperate instance of veracrypt and for each of these instances pipes are created in line 23.

    The fork in line 26 splits the program in one instance that asks for the passphrase (line 60 onwards) and another instance that passes the passphrase (line 29 onwards) to veracrypt (line 56).
    The for-loop in line 41 forks the child process again for each container/mountpoint pair given as an commandline argument. The for-loop breaks for the parent (line 51) and the value of i (stored in j) is used to refer to that pair, when calling execlp in line 56.



    > 1. I wonder, whether the entered password remains anywhere in the storage after the execution of the program is finished
    > (this is the whole point of this code, because otherwise I could have used just a shell script).
    No more or less than if you'd done it as a shell script.

    Sure, yes, technically, destroying the password sooner is better.
    But destroy and then exit is barely different to just exit (as a shell-script would).
    Exactly. And because of that difference, I wrote this program, to make sure the password will be properly destroyed (line 74)

    If an attacker is already on your machine with the ability to read memory of any arbitrary process, you've already lost the game.
    The much easier option for your attacker is to simply strace the process and observe the data being passed in read() and write() calls.
    That's for sure. But that's also the case when calling veracrypt directly. I just want my program to be as secure as if I would enter the password 5 times (for 5 containers) directly.
    1. It's obsolete.
    Is there an alternative to getpass() available? It is a pretty useful function, there must be something.

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    You're waiting for a bunch of external processes that take seconds to complete.

    Are you REALLY sure that the 0.1% time difference between you clearing the memory and the program exiting (and the OS erasing that memory), and a shell script exiting with the password still intact (and the OS erasing that memory) is going to make a bean of difference to your security?

    You've added an extra row of bricks to the top of the castle wall, but left the front gate open.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  5. #5
    Registered User
    Join Date
    Jul 2019
    Posts
    6
    Quote Originally Posted by Salem View Post
    Are you REALLY sure that the 0.1% time difference between you clearing the memory and the program exiting (and the OS erasing that memory), and a shell script exiting with the password still intact (and the OS erasing that memory) is going to make a bean of difference to your security?
    According to my (very basic) understanding of memory management, the storage location of an environment variable of a shell script is made available again, but not overwritten, when the script exits. In line 74 my program overwrites the data stored on that location with random values.

    Am I mistaken, and does a shell script destroy the data allocated by the script aswell? Wouldn't that be just unnecessary and time consuming in most cases?

  6. #6
    Registered User
    Join Date
    Feb 2019
    Posts
    1,078
    Quote Originally Posted by oneone View Post
    According to my (very basic) understanding of memory management, the storage location of an environment variable of a shell script is made available again, but not overwritten, when the script exits. In line 74 my program overwrites the data stored on that location with random values.
    Environment variables are kept by the parent process (not the 'shell') and inherited by child processes. This can demonstrate this behavior:

    Code:
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <errno.h>
    #include <sys/wait.h>
    
    int main( void )
    {
      int status;
      char *e;
    
      // setting environment var NOT using the shell.
      setenv( "MYVAR", "1", 0 );
      puts( "Parent process set MYVAR with '1'.");
    
      errno = 0;
      switch ( fork() )
      {
        case 0: // child process
         // The child process inherites the environment from its parent!
          if ( ! ( e = getenv( "MYVAR" ) ) )
          {
            fputs( "Cannot get MYVAR environment variable.\n", stderr );
            return EXIT_FAILURE;
          }
          printf( "Child reads MYVAR with '%s'.\n", e );
          break;
    
        case -1:  // fork error
          perror( "fork" );
          return EXIT_FAILURE;
    
        default: // parent (current) process
          // Here's how you get the return status from a child process.
          // Since we have just one forked process, wait() will be sufficient.
          // Use waitpid() for multiple forked processes.
          wait( &status );
    
          // wait() will wait for any status changes, including signals (SIGSTOP, SIGCONT).
          // since we're not dealing with those here. It's safe to assume wait() will return 
          // on child process exit.
          if ( WIFEXITED( status ) )
            printf( "Child process exited with status %d.\n", WEXITSTATUS( status ) );
      }
    
      return EXIT_SUCCESS;
    }
    Am I mistaken, and does a shell script destroy the data allocated by the script aswell? Wouldn't that be just unnecessary and time consuming in most cases?
    Not "destroys" but deallocate, probably.

    As for getpass(). You can use fgets() ou getline(), using tcgetattr() and tcsetattr() (termios) to turn ECHO off.
    Last edited by flp1969; 07-14-2019 at 06:51 AM.

  7. #7
    Registered User
    Join Date
    Jul 2019
    Posts
    6
    Quote Originally Posted by flp1969 View Post
    Environment variables are kept by the parent process (not the 'shell') and inherited by child processes.
    Well, when calling a program from shell (bash, csh, zsh or whatever shell is in use), the shell fork()s and exec()s the called program and becomes the parent process. That is why environment variables from the shell are accessible in the called program. But I don't really see, how this is related to any of my problems. I was just talking about a hypothetical shell-script which might pipe the value of a variable to veracrypt. But let's not go deeper into this. I think the approach via C will be fine. Thanks for the explanation though.

    Not "destroys" but deallocate, probably.
    This is what I mean. So I aim to "destroy" the data properly. It is just because I don't feel so comfortable with my password remaining somewhere in RAM till it gets overwritten by chance. This is all I care about.

    As for getpass(). You can use fgets() ou getline(), using tcgetattr() and tcsetattr() (termios) to turn ECHO off.
    Thanks for this. I will implement this.

    Here's how you get the return status from a child process. Since we have just one forked process, wait() will be sufficient.
    Also thanks for that hint. I created the function success(char*,char*), which basically waits for the child. So here is the problem that I came across: I can wait for the child created at first without any problems (line 102). But I want to check whether all volumes where mounted properly (and if not, print an error message, which one wasn't). For some reason, which I don't understand, waiting for the children of the child (created in line 59), waiting (line 69) doesn't work. See the following code:

    Code:
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>
    #include <stdbool.h>
    #include <sys/wait.h>
    
    
    #define READ_END 0   
    #define WRITE_END 1 
    
    
    void success(char *container, char *mountpoint)
    {
                    int status;
    
    
                    printf("\nDEBUG: Waiting for mounting of \t%s\tat %s\n", container, mountpoint);
    
    
                    wait(&status);
                    if(status == 0) printf("Successfully mounted \t%s\tat %s\n", container, mountpoint);
                    else {
                        printf("Error mounting \t%s\tat %s\t<< return value:%d\n", container, mountpoint, status);
                        exit(2);
                    }
    }
    
    
    int main(int argc, char* argv[])
    {
        if(argc > 1 && argc%2==1)
        {
            int inst = (argc-1)/2;
            int     fd[inst][2];
            pid_t   pid;
            char    scmd[] = "veracrypt";
    
    
            for(int i = 0; i<inst;i++){
                pipe(fd[i]);
            }
            pid = fork();
    
    
            if(pid==0)
            {
                    int j=0;
    
    
                    dup2(fd[0][READ_END], STDIN_FILENO);
                    close(fd[0][WRITE_END]);
                    close(fd[0][READ_END]);
    
    
                    bool b = true;
                    for(int i=0;i<inst-1 && b;i++){
                        pid = fork();
                        if(pid==0){
                            j = i+1;
                            dup2(fd[j][READ_END], STDIN_FILENO);
                            close(fd[j][WRITE_END]);
                            close(fd[j][READ_END]);
                        }
                        else{
                                j = i;
                                b = false;
                                //success(argv[(j+1)*2+1],argv[(j+1)*2+2]);
                        }
                    }
    
    
                    printf("\nDEBUG: Attempting to mount \t%s\tat %s\n", argv[(j)*2+1],argv[(j)*2+2]);
    
    
                    //freopen("/dev/null", "w", stdout);
    
    
                    execlp(scmd, scmd, "-t","--protect-hidden=no","--keyfiles=","--pim=123",argv[j*2+1],argv[j*2+2],(char*) NULL);
                    fprintf(stderr, "Failed to execute '%s'\n", scmd);
                    exit(1);
            }
            else
            { 
                    char *buffer = getpass("Veracrypt Password:");
    
    
                    for(int i=0;i<inst;i++)
                    {
                        close(fd[i][READ_END]);
                        write(fd[i][WRITE_END], buffer, (strlen(buffer)+1));
                    }
                    for(int i=0;i<inst;i++){close(fd[i][WRITE_END]);}
    
    
                    srand(time(0));
                    for(int i = 0; i<strlen(buffer); i++) buffer[i] = rand();
    
    
                    free(buffer);
                    success(argv[1],argv[2]);
            }
        }
        return(0);
    }

    If compiled like above, the program runs as expected, when entering a wrong password:

    Code:
    % ./myprogram /path/to/container1 /path/to/mountpoint1 /path/to/container2 /path/to/mountpoint2
    Veracrypt Password:
    DEBUG: Attempting to mount        /path/to/container1 at /path/to/mountpoint1
    
    
    DEBUG: Attempting to mount        /path/to/container2 at /path/to/mountpoint2
    
    
    
    
    DEBUG: Waiting for mounting of  /path/to/container1 at /path/to/mountpoint1
    Enter password for /path/to/container1:
    Operation failed due to one or more of the following:
     - Incorrect password.
     - Incorrect Volume PIM number.
     - Incorrect PRF (hash).
     - Not a valid volume.
    
    
    Enter password for /path/to/container1: Enter password for /path/to/container2:
    Error mounting   /path/to/container1  at /path/to/mountpoint1 << return value:256
    % Operation failed due to one or more of the following:
     - Incorrect password.
     - Incorrect Volume PIM number.
     - Incorrect PRF (hash).
     - Not a valid volume.
    
    
    Enter password for /path/to/container2:
    I know, the output is very ugly (also because it is mixed with the veracrypt output), but it is just for debugging purposes.
    The output shows that both children are started. After veracrypt fails to mount container1, veracrypt's verbose error message is printed to the screen and then my error message is printed, the parent exits and I am back in my shell (notice the "%" in the output). The second veracrypt process still runs in background and generates its output.

    If I remove the comment in line 69 and call the function success(char*,char*) at that point, things stop working:

    Code:
    % ./myprogram /path/to/container1 /path/to/mountpoint1 /path/to/container2 /path/to/mountpoint2
    Veracrypt Password:
    DEBUG: Attempting to mount        /path/to/container1 at /path/to/mountpoint1
    
    
    DEBUG: Attempting to mount        /path/to/container2 at /path/to/mountpoint2
    
    
    
    
    DEBUG: Waiting for mounting of  /path/to/container1 at /path/to/mountpoint1
    This is the whole output. I consider this behaviour rather weird, since execlp is called immediately after printing the "Attempting to mount" message, but veracrypt doesn't generate any output. It appears in the process list though.

    (Calling just "./myprogram /path/to/container1 /path/to/mountpoint1" works fine in both cases, since line 69 is not executed in that case)

    I hope this explanation of my problem will be sufficient for understanding, what's going on. If I was ambiguous or did not elaborate enough on anything, just ask.

    Maybe it is really just a very stupid thing, that I've overlooked, but I appreciate any help with this. It is the first time I work with fork()s and there's still much to learn.
    Also nesting several fork()s and this array of several pipes seems kind of unprofessional to me. If there is a better better solution to call several instances of a program (veracrypt) and piping data to them, I would be glad to hear about that.

    Thanks to everyone, who is digging through this code!!!

    oneone.
    Last edited by oneone; 07-14-2019 at 04:05 PM.

  8. #8
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    Yes, you are very much mistaken as to how shell variables work.
    For one thing, environment variables only propagate from parent to child, not the other way round. So once a script actually exits, anything it set locally is truly gone.
    For another, a shell script has to explicitly use the 'export' command to make a variable visible to child processes.

    Instead of running veracrypt, compile this program and run it instead.
    Code:
    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    extern char **environ;
    
    int main(int argc, char *argv[]) {
      printf("PID=%d, PPID=%d\n", (int)getpid(), (int)getppid());
      printf("Dumping Environment\n");
      for ( int i = 0 ; environ[i] != NULL ; i++ ) {
        printf("%d:%s\n", i, environ[i]);
      }
      printf("Dumping command line\n");
      for ( int i = 0 ; i < argc ; i++ ) {
        printf("%d:%s\n", i, argv[i]);
      }
      printf("Dumping stdin\n");
      char buff[BUFSIZ];
      while(fgets(buff,BUFSIZ,stdin)) {
        printf("%s",buff);
      }
      return 0;
    }
    Eg.
    Code:
    $ ./a.out hello world
    PID=3805, PPID=3134
    Dumping Environment
    0:XDG_VTNR=7
    1:XDG_SESSION_ID=c2
    2:CLUTTER_IM_MODULE=xim
    <<snipped>>
    62:_=./a.out
    63:OLDPWD=/home/me
    Dumping command line
    0:./a.out
    1:hello
    2:world
    Dumping stdin
    boo
    boo
    yah
    yah
    Use this to explore what environment variables propagate from your shell to whatever process you spawn, what command lines you construct and what additional input you pipe into it through stdin.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  9. #9
    Registered User
    Join Date
    Jul 2019
    Posts
    6
    Quote Originally Posted by Salem View Post
    For another, a shell script has to explicitly use the 'export' command to make a variable visible to child processes.
    Thank you very much for pointing this out. I didn't know that.

    Quote Originally Posted by Salem View Post
    Instead of running veracrypt, compile this program and run it instead.
    [...]
    Use this to explore what environment variables propagate from your shell to whatever process you spawn, what command lines you construct and what additional input you pipe into it through stdin.
    I will do so. This looks like the debugging instrument, that could reveal the source of my problem.

  10. #10
    Registered User
    Join Date
    Jul 2019
    Posts
    6

    [solved]

    I managed to find a solution to my problem now (thanks to Salem's suggestion). It also helped me to develop a better understanding of how pipes work. The problem was that inside the tree of nested forks the first child had one open pipe, the second one two, the third one three and so on. Without letting the processes wait for their children there was no problem, because for a number of n open pipes n veracrypt instances read the data and quit afterwards. But when waiting for the n-th process before letting the (n-1)-th process read the pipe, all the other pipes opened by the n-th process remained open.
    The trick was to have just one open pipe in the last forked child process. Then two pipes opened by its parent and so on (so exactly the other way around). I really struggle to explain this properly by words at this point, so I will let the code speak for me:

    Code:
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>
    #include <stdbool.h>
    #include <sys/wait.h>
    
    
    #define READ_END 0   
    #define WRITE_END 1 
    
    
    void success(char *container, char *mountpoint)
    {
                    int status;
    
    
                    wait(&status);
                    if(status == 0) printf("Successfully mounted \t%s\tat %s\n", container, mountpoint);
                    else {
                        printf("Error mounting \t%s\tat %s\t<< return value:%d\n", container, mountpoint, status);
                    }
    }
    
    
    int main(int argc, char* argv[])
    {
        if(argc > 1 && argc%2==1)
        {
            int inst = (argc-1)/2;
            int     fd[inst][2];
            pid_t   pid;
            char    scmd[] = "veracrypt";
    
    
            for(int i = 0; i<inst;i++){
                pipe(fd[i]);
            }
            pid = fork();
    
    
            if(pid==0)
            {
                    int j=0;
    
    
                    dup2(fd[inst-1][READ_END], STDIN_FILENO);
                    close(fd[inst-1][WRITE_END]);
                    close(fd[inst-1][READ_END]);
    
    
                    bool b = true;
                    for(int i=0;i<inst-1 && b;i++){
                        pid = fork();
                        if(pid==0){
                            j = i+1;
                        }
                        else{
                                j = i;
                                b = false;
                                for(int i=0;i<inst-1-j;i++){
                                        dup2(fd[i][READ_END], STDIN_FILENO);
                                        close(fd[i][WRITE_END]);
                                        close(fd[i][READ_END]);
                                }
                                success(argv[(j+1)*2+1],argv[(j+1)*2+2]);
                        }
                    }
    
    
                    freopen("/dev/null", "w", stdout);
    
    
                    execlp(scmd, scmd, "-t","--protect-hidden=no","--keyfiles=","--pim=123",argv[j*2+1],argv[j*2+2],(char*) NULL);
                    fprintf(stderr, "Failed to execute '%s'\n", scmd);
                    exit(1);
            }
            else
            { 
                    char *buffer = getpass("Veracrypt Password:");
    
    
                    for(int i=0;i<inst;i++)
                    {
                        close(fd[i][READ_END]);
                        write(fd[i][WRITE_END], buffer, (strlen(buffer)+1));
                        close(fd[i][WRITE_END]);
                    }
    
                    srand(time(0));
                    for(int i = 0; i<strlen(buffer); i++) buffer[i] = rand();
    
                    free(buffer);
                    success(argv[1],argv[2]);
            }
        }
        return(0);
    }
    I still think there must be a nicer solution than these nested forks, but for now I regard this problem as solved. Thanks for the hints.
    Also I still have to get rid of the deprecated getpass() function. But that shouldn't be much of a hassle.
    Last edited by oneone; 07-18-2019 at 08:56 AM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 1
    Last Post: 01-09-2017, 03:40 AM
  2. Replies: 1
    Last Post: 06-29-2014, 02:08 AM
  3. help me with piping...
    By haj1st in forum C Programming
    Replies: 10
    Last Post: 02-23-2010, 12:23 AM
  4. Replies: 2
    Last Post: 01-07-2009, 10:35 AM
  5. piping....
    By coolDUDE in forum C Programming
    Replies: 1
    Last Post: 11-18-2002, 05:42 AM

Tags for this Thread