/* the volatile qualifiers should be removed as well. */ int callmgr_main(int argc, char **argv, char **envp) { struct in_addr inetaddr; int inet_sock, unix_sock; fd_set call_set; PPTP_CONN * conn; VECTOR * call_list; int max_fd=0; volatile int first=1; int retval; int i; char * volatile phonenr; /* Step 0: Check arguments */ if (argc < 2) fatal("Usage: %s ip.add.ress.here [--phone <phone number>]", argv[0]); phonenr = argc==3 ? argv[2] : NULL; if (inet_aton(argv[1], &inetaddr)==0) fatal("Invalid IP address: %s", argv[1]); /* Step 1: Open sockets. */ if ((inet_sock = open_inetsock(inetaddr)) < 0) fatal("Could not open control connection to %s", argv[1]); if ((unix_sock = open_unixsock(inetaddr)) < 0) fatal("Could not open unix socket for %s", argv[1]); /* Step 1b: FORK and return status to calling process. */ switch (fork()) { case 0: /* child. stick around. */ break; case -1: /* failure. Fatal. */ fatal("Could not fork."); default: /* Parent. Return status to caller. */ exit(0); } /* re-open stderr as /dev/null to release it */ file2fd("/dev/null", "wb", STDERR_FILENO); /* Step 1c: Clean up unix socket on TERM */ if (sigsetjmp(callmgr_env, 1)!=0) goto cleanup; signal(SIGINT, callmgr_sighandler); signal(SIGTERM, callmgr_sighandler); signal(SIGPIPE, callmgr_do_nothing); signal(SIGUSR1, callmgr_do_nothing); /* signal state change; wake up accept */ /* Step 2: Open control connection and register callback */ if ((conn = pptp_conn_open(inet_sock, 1, NULL/* callback */)) == NULL) { close(unix_sock); close(inet_sock); fatal("Could not open connection."); } FD_ZERO(&call_set); max_fd = unix_sock; call_list = vector_create(); { struct local_conninfo *conninfo = malloc(sizeof(*conninfo)); if (conninfo==NULL) { close(unix_sock); close(inet_sock); fatal("No memory."); } conninfo->call_list = call_list; conninfo->call_set = &call_set; pptp_conn_closure_put(conn, conninfo); } if (sigsetjmp(callmgr_env, 1)!=0) goto shutdown; /* Step 3: Get FD_SETs */ do { int rc; fd_set read_set = call_set, write_set; FD_ZERO (&write_set); FD_SET (unix_sock, &read_set); pptp_fd_set(conn, &read_set, &write_set, &max_fd); for (; max_fd > 0 ; max_fd--) { if (FD_ISSET (max_fd, &read_set) || FD_ISSET (max_fd, &write_set)) break; } /* Step 4: Wait on INET or UNIX event */ if ((rc = select(max_fd+1, &read_set, &write_set, NULL, NULL)) <0) /* a signal or somesuch. */ continue; /* Step 5a: Handle INET events */ pptp_dispatch(conn, &read_set, &write_set); /* Step 5b: Handle new connection to UNIX socket */ if (FD_ISSET(unix_sock, &read_set)) { /* New call! */ struct sockaddr_un from; int len = sizeof(from); PPTP_CALL * call; struct local_callinfo *lci; int s; /* Accept the socket */ FD_CLR (unix_sock, &read_set); if ((s = accept(unix_sock, (struct sockaddr *) &from, &len))<0) { warn("Socket not accepted: %s", strerror(errno)); goto skip_accept; } /* Allocate memory for local call information structure. */ if ((lci = malloc(sizeof(*lci))) == NULL) { warn("Out of memory."); close(s); goto skip_accept; } lci->unix_sock = s; /* Give the initiator time to write the PIDs while we open the call */ call = pptp_call_open(conn, call_callback, phonenr); /* Read and store the associated pids */ read(s, &lci->pid[0], sizeof(lci->pid[0])); read(s, &lci->pid[1], sizeof(lci->pid[1])); /* associate the local information with the call */ pptp_call_closure_put(conn, call, (void *) lci); /* The rest is done on callback. */ /* Keep alive; wait for close */ retval = vector_insert(call_list, s, call); assert(retval); if (s > max_fd) max_fd = s; FD_SET(s, &call_set); first = 0; } skip_accept: /* Step 5c: Handle socket close */ for (i=0; i<max_fd+1; i++) if (FD_ISSET(i, &read_set)) { /* close it */ PPTP_CALL * call; retval = vector_search(call_list, i, &call); if (retval) { struct local_callinfo *lci = pptp_call_closure_get(conn, call); log("Closing connection"); if(lci->pid[0]) kill(lci->pid[0], SIGTERM); if(lci->pid[1]) kill(lci->pid[1], SIGTERM); free(lci); /* soft shutdown. Callback will do hard shutdown later */ pptp_call_close(conn, call); vector_remove(call_list, i); } FD_CLR(i, &call_set); close(i); } } while (vector_size(call_list)>0 || first); shutdown: { fd_set read_set, write_set; /* warn("Shutdown"); */ /* kill all open calls */ for (i=0; i<vector_size(call_list); i++) { PPTP_CALL *call = vector_get_Nth(call_list, i); struct local_callinfo *lci = pptp_call_closure_get(conn, call); log("Closing connection"); pptp_call_close(conn, call); if(lci->pid[0]) kill(lci->pid[0], SIGTERM); if(lci->pid[1]) kill(lci->pid[1], SIGTERM); } /* attempt to dispatch these messages */ FD_ZERO(&read_set); FD_ZERO(&write_set); pptp_fd_set(conn, &read_set, &write_set, &max_fd); FD_ZERO(&read_set); pptp_dispatch(conn, &read_set, &write_set); if (i>0) sleep(2); /* no more open calls. Close the connection. */ pptp_conn_close(conn, PPTP_STOP_LOCAL_SHUTDOWN); FD_ZERO(&read_set); FD_ZERO(&write_set); pptp_fd_set(conn, &read_set, &write_set, &max_fd); FD_ZERO(&read_set); pptp_dispatch(conn, &read_set, &write_set); sleep(2); /* with extreme prejudice */ pptp_conn_destroy(conn); vector_destroy(call_list); } cleanup: close_inetsock(inet_sock, inetaddr); close_unixsock(unix_sock, inetaddr); return 0; }
/* TODO: redesign to avoid longjmp/setjmp. Several variables here have a volatile qualifier to silence warnings from gcc < 3.0. Remove the volatile qualifiers if longjmp/setjmp are removed. */ int main(int argc, char **argv, char **envp) { struct in_addr inetaddr; volatile int callmgr_sock = -1; char ttydev[PATH_MAX]; int pty_fd, tty_fd, gre_fd, rc; volatile pid_t parent_pid, child_pid; u_int16_t call_id, peer_call_id; char buf[128]; int pppdargc; char **pppdargv; char phonenrbuf[65]; /* maximum length of field plus one for the trailing * '\0' */ char * volatile phonenr = NULL; volatile int launchpppd = 1, debug = 0, nodaemon = 0; while(1){ /* structure with all recognised options for pptp */ static struct option long_options[] = { {"phone", 1, 0, 0}, {"nolaunchpppd", 0, 0, 0}, {"quirks", 1, 0, 0}, {"debug", 0, 0, 0}, {"sync", 0, 0, 0}, {"timeout", 1, 0, 0}, {"logstring", 1, 0, 0}, {"localbind", 1, 0, 0}, {"loglevel", 1, 0, 0}, {"nobuffer", 0, 0, 0}, {"idle-wait", 1, 0, 0}, {"max-echo-wait", 1, 0, 0}, {"version", 0, 0, 0}, {"nodaemon", 0, 0, 0}, {0, 0, 0, 0} }; int option_index = 0; int c; c = getopt_long (argc, argv, "", long_options, &option_index); if (c == -1) break; /* no more options */ switch (c) { case 0: if (option_index == 0) { /* --phone specified */ /* copy it to a buffer, as the argv's will be overwritten * by inststr() */ strncpy(phonenrbuf,optarg,sizeof(phonenrbuf)); phonenrbuf[sizeof(phonenrbuf) - 1] = '\0'; phonenr = phonenrbuf; } else if (option_index == 1) {/* --nolaunchpppd specified */ launchpppd = 0; } else if (option_index == 2) {/* --quirks specified */ if (set_quirk_index(find_quirk(optarg))) usage(argv[0]); } else if (option_index == 3) {/* --debug */ debug = 1; } else if (option_index == 4) {/* --sync specified */ syncppp = 1; } else if (option_index == 5) {/* --timeout */ float new_packet_timeout = atof(optarg); if (new_packet_timeout < 0.0099 || new_packet_timeout > 10) { fprintf(stderr, "Packet timeout %s (%f) out of range: " "should be between 0.01 and 10 seconds\n", optarg, new_packet_timeout); log("Packet timeout %s (%f) out of range: should be" "between 0.01 and 10 seconds", optarg, new_packet_timeout); exit(2); } else { packet_timeout_usecs = new_packet_timeout * 1000000; } } else if (option_index == 6) {/* --logstring */ log_string = strdup(optarg); } else if (option_index == 7) {/* --localbind */ if (inet_pton(AF_INET, optarg, (void *) &localbind) < 1) { fprintf(stderr, "Local bind address %s invalid\n", optarg); log("Local bind address %s invalid\n", optarg); exit(2); } } else if (option_index == 8) { /* --loglevel */ log_level = atoi(optarg); if (log_level < 0 || log_level > 2) usage(argv[0]); } else if (option_index == 9) { /* --nobuffer */ disable_buffer = 1; } else if (option_index == 10) { /* --idle-wait */ int x = atoi(optarg); if (x < 0) { fprintf(stderr, "--idle-wait must not be negative\n"); log("--idle-wait must not be negative\n"); exit(2); } else { idle_wait = x; } } else if (option_index == 11) { /* --max-echo-wait */ int x = atoi(optarg); if (x < 0) { fprintf(stderr, "--max-echo-wait must not be negative\n"); log("--max-echo-wait must not be negative\n"); exit(2); } else { max_echo_wait = x; } fprintf(stderr, "--max-echo-wait ignored, not yet implemented\n"); } else if (option_index == 12) { /* --version */ fprintf(stdout, "%s\n", version); exit(0); } else if (option_index == 13) {/* --nodaemon */ nodaemon = 1; } break; case '?': /* unrecognised option */ /* fall through */ default: usage(argv[0]); } if (c == -1) break; /* no more options for pptp */ } /* at least one argument is required */ if (argc <= optind) usage(argv[0]); /* Get IP address for the hostname in argv[1] */ inetaddr = get_ip_address(argv[optind]); optind++; /* Find the ppp options, extract phone number */ pppdargc = argc - optind; pppdargv = argv + optind; log("The synchronous pptp option is %sactivated\n", syncppp ? "" : "NOT "); /* Now we have the peer address, bind the GRE socket early, before starting pppd. This prevents the ICMP Unreachable bug documented in <1026868263.2855.67.camel@jander> */ gre_fd = pptp_gre_bind(inetaddr); if (gre_fd < 0) { close(callmgr_sock); fatal("Cannot bind GRE socket, aborting."); } /* Find an open pty/tty pair. */ if(launchpppd){ rc = openpty (&pty_fd, &tty_fd, ttydev, NULL, NULL); if (rc < 0) { close(callmgr_sock); fatal("Could not find free pty."); } #if defined(__linux__) /* * if we do not turn off echo now, then if pppd sleeps for any * length of time (DNS timeouts or whatever) the other end of the * connect may detect the link as looped. Other OSen may want this * as well, but for now linux gets it. */ { struct termios tios; tcgetattr(tty_fd, &tios); cfmakeraw(&tios); tcsetattr(tty_fd, TCSAFLUSH, &tios); } #endif /* fork and wait. */ signal(SIGUSR1, do_nothing); /* don't die */ signal(SIGCHLD, do_nothing); /* don't ignore SIGCHLD */ parent_pid = getpid(); switch (child_pid = fork()) { case -1: fatal("Could not fork pppd process"); case 0: /* I'm the child! */ close (tty_fd); signal(SIGUSR1, SIG_DFL); child_pid = getpid(); break; default: /* parent */ close (pty_fd); close (gre_fd); /* * There is still a very small race condition here. If a signal * occurs after signaled is checked but before pause is called, * things will hang. */ if (!signaled) { pause(); /* wait for the signal */ } if (signaled == SIGCHLD) fatal("Child process died"); launch_pppd(ttydev, pppdargc, pppdargv); /* launch pppd */ perror("Error"); fatal("Could not launch pppd"); } } else { /* ! launchpppd */ pty_fd = tty_fd = STDIN_FILENO; /* close unused file descriptor, that is redirected to the pty */ close(STDOUT_FILENO); child_pid = getpid(); parent_pid = 0; /* don't kill pppd */ } do { /* * Open connection to call manager (Launch call manager if necessary.) */ callmgr_sock = open_callmgr(inetaddr, phonenr, argc, argv, envp, pty_fd, gre_fd); /* Exchange PIDs, get call ID */ } while (get_call_id(callmgr_sock, parent_pid, child_pid, &call_id, &peer_call_id) < 0); /* Send signal to wake up pppd task */ if (launchpppd) { kill(parent_pid, SIGUSR1); sleep(2); /* become a daemon */ if (!(debug || nodaemon) && daemon(0, 0) != 0) { perror("daemon"); } } else { /* re-open stderr as /dev/null to release it */ file2fd("/dev/null", "wb", STDERR_FILENO); } snprintf(buf, sizeof(buf), "pptp: GRE-to-PPP gateway on %s", ttyname(tty_fd)); #ifdef PR_SET_NAME rc = prctl(PR_SET_NAME, "pptpgw", 0, 0, 0); if (rc != 0) perror("prctl"); #endif inststr(argc, argv, envp, buf); if (sigsetjmp(env, 1)!= 0) goto shutdown; signal(SIGINT, sighandler); signal(SIGTERM, sighandler); signal(SIGKILL, sighandler); signal(SIGCHLD, sighandler); signal(SIGUSR1, sigstats); /* Do GRE copy until close. */ pptp_gre_copy(call_id, peer_call_id, pty_fd, gre_fd); shutdown: /* on close, kill all. */ if(launchpppd) kill(parent_pid, SIGTERM); close(pty_fd); close(callmgr_sock); exit(0); }