/* * pptp_handle_ctrl_connection * * 1. read a packet (should be start_ctrl_conn_rqst) * 2. reply to packet (send a start_ctrl_conn_rply) * 3. proceed with GRE and CTRL connections * * args: pppaddrs - ppp local and remote addresses (strings) * inetaddrs - local and client socket address * retn: 0 success, -1 failure */ static void pptp_handle_ctrl_connection(char **pppaddrs, struct in_addr *inetaddrs) { /* For echo requests used to check link is alive */ int echo_wait = FALSE; /* Waiting for echo? */ u_int32_t echo_count = 0; /* Sequence # of echo */ time_t echo_time = 0; /* Time last echo req sent */ struct timeval idleTime; /* How long to select() */ /* General local variables */ ssize_t rply_size; /* Reply packet size */ fd_set fds; /* For select() */ int maxfd = clientSocket; /* For select() */ int send_packet; /* Send a packet this time? */ #if BSDUSER_PPP || SLIRP /* not needed by stuff which uses socketpair() in startCall() */ #define init 1 #else int init = 0; /* Has pppd initialized the pty? */ #endif int pty_fd = -1; /* File descriptor of pty */ int gre_fd = -1; /* Network file descriptor */ int sig_fd = sigpipe_fd(); /* Signal pipe descriptor */ unsigned char packet[PPTP_MAX_CTRL_PCKT_SIZE]; unsigned char rply_packet[PPTP_MAX_CTRL_PCKT_SIZE]; for (;;) { FD_ZERO(&fds); FD_SET(sig_fd, &fds); FD_SET(clientSocket, &fds); if (pty_fd != -1) FD_SET(pty_fd, &fds); if (gre_fd != -1 && init) FD_SET(gre_fd, &fds); /* set timeout */ if (encaps_gre(-1, NULL, 0) || decaps_hdlc(-1, NULL, 0)) { idleTime.tv_sec = 0; idleTime.tv_usec = 50000; /* don't ack immediately */ } else { idleTime.tv_sec = IDLE_WAIT; idleTime.tv_usec = 0; } /* default: do nothing */ send_packet = FALSE; switch (select(maxfd + 1, &fds, NULL, NULL, &idleTime)) { case -1: /* Error with select() */ if (errno != EINTR) syslog(LOG_ERR, "CTRL: Error with select(), quitting"); goto leave_clear_call; case 0: if (decaps_hdlc(-1, NULL, 0)) { if(decaps_hdlc(-1, encaps_gre, gre_fd)) syslog(LOG_ERR, "CTRL: GRE re-xmit failed"); } else if (encaps_gre(-1, NULL, 0)) /* Pending ack and nothing else to do */ encaps_gre(gre_fd, NULL, 0); /* send ack with no payload */ else if (echo_wait != TRUE) { /* Timeout. Start idle link detection. */ echo_count++; if (pptpctrl_debug) syslog(LOG_DEBUG, "CTRL: Sending ECHO REQ id %d", echo_count); time(&echo_time); make_echo_req_packet(rply_packet, &rply_size, echo_count); echo_wait = TRUE; send_packet = TRUE; } break; default: break; } /* check for pending SIGTERM delivery */ if (FD_ISSET(sig_fd, &fds)) { if (sigpipe_read() == SIGTERM) bail(SIGTERM); } /* detect startup of pppd */ #ifndef init if (!init && pty_fd != -1 && FD_ISSET(pty_fd, &fds)) init = 1; #endif /* handle actual packets */ /* send from pty off via GRE */ if (pty_fd != -1 && FD_ISSET(pty_fd, &fds) && decaps_hdlc(pty_fd, encaps_gre, gre_fd) < 0) { syslog(LOG_ERR, "CTRL: PTY read or GRE write failed (pty,gre)=(%d,%d)", pty_fd, gre_fd); break; } /* send from GRE off to pty */ if (gre_fd != -1 && FD_ISSET(gre_fd, &fds) && decaps_gre(gre_fd, encaps_hdlc, pty_fd) < 0) { if (gre_fd == 6 && pty_fd == 5) { syslog(LOG_ERR, "CTRL: GRE-tunnel has collapsed (GRE read or PTY write failed (gre,pty)=(%d,%d))", gre_fd, pty_fd); } else { syslog(LOG_ERR, "CTRL: GRE read or PTY write failed (gre,pty)=(%d,%d)", gre_fd, pty_fd); } break; } /* handle control messages */ if (FD_ISSET(clientSocket, &fds)) { send_packet = TRUE; switch (read_pptp_packet(clientSocket, packet, rply_packet, &rply_size)) { case 0: syslog(LOG_ERR, "CTRL: CTRL read failed"); goto leave_drop_call; case -1: send_packet = FALSE; break; case STOP_CTRL_CONN_RQST: if (pptpctrl_debug) syslog(LOG_DEBUG, "CTRL: Received STOP CTRL CONN request (disconnecting)"); if (gre_fd != -1 || pty_fd != -1) syslog(LOG_WARNING, "CTRL: Request to close control connection when call is open, closing"); send_pptp_packet(clientSocket, rply_packet, rply_size); goto leave_drop_call; case CALL_CLR_RQST: if(pptpctrl_debug) syslog(LOG_DEBUG, "CTRL: Received CALL CLR request (closing call)"); if (gre_fd == -1 || pty_fd == -1) syslog(LOG_WARNING, "CTRL: Request to close call but call not open"); if (gre_fd != -1) { FD_CLR(gre_fd, &fds); close(gre_fd); gre_fd = -1; } if (pty_fd != -1) { FD_CLR(pty_fd, &fds); close(pty_fd); pty_fd = -1; } /* violating RFC */ goto leave_drop_call; case OUT_CALL_RQST: /* for killing off the link later (ugly) */ NOTE_VALUE(PAC, call_id_pair, ((struct pptp_out_call_rply *) (rply_packet))->call_id); NOTE_VALUE(PNS, call_id_pair, ((struct pptp_out_call_rply *) (rply_packet))->call_id_peer); if (gre_fd != -1 || pty_fd != -1) { syslog(LOG_WARNING, "CTRL: Request to open call when call is already open, closing"); if (gre_fd != -1) { FD_CLR(gre_fd, &fds); close(gre_fd); gre_fd = -1; } if (pty_fd != -1) { FD_CLR(pty_fd, &fds); close(pty_fd); pty_fd = -1; } } /* change process title for accounting and status scripts */ my_setproctitle(gargc, gargv, "pptpd [%s:%04X - %04X]", inet_ntoa(inetaddrs[1]), ntohs(((struct pptp_out_call_rply *) (rply_packet))->call_id_peer), ntohs(((struct pptp_out_call_rply *) (rply_packet))->call_id)); /* start the call, by launching pppd */ syslog(LOG_INFO, "CTRL: Starting call (launching pppd, opening GRE)"); pty_fd = startCall(pppaddrs, inetaddrs); if (pty_fd > maxfd) maxfd = pty_fd; if ((gre_fd = pptp_gre_init(call_id_pair, pty_fd, inetaddrs)) > maxfd) maxfd = gre_fd; break; case ECHO_RPLY: if (echo_wait == TRUE && ((struct pptp_echo_rply *) (packet))->identifier == echo_count) echo_wait = FALSE; else syslog(LOG_WARNING, "CTRL: Unexpected ECHO REPLY packet"); /* FALLTHRU */ case SET_LINK_INFO: send_packet = FALSE; break; #ifdef PNS_MODE case IN_CALL_RQST: case IN_CALL_RPLY: case IN_CALL_CONN: #endif case CALL_DISCONN_NTFY: case STOP_CTRL_CONN_RPLY: /* These don't generate replies. Also they come from things we don't send in this section. */ syslog(LOG_WARNING, "CTRL: Got a reply to a packet we didn't send"); send_packet = FALSE; break; /* Otherwise, the already-formed reply will do fine, so send it */ } } /* send reply packet - this may block, but it should be very rare */ if (send_packet == TRUE && send_pptp_packet(clientSocket, rply_packet, rply_size) < 0) { syslog(LOG_ERR, "CTRL: Error sending GRE, aborting call"); goto leave_clear_call; } /* waiting for echo reply and curtime - echo_time > max wait */ if (echo_wait == TRUE && (time(NULL) - echo_time) > MAX_ECHO_WAIT) { syslog(LOG_INFO, "CTRL: Session timed out, ending call"); goto leave_clear_call; } } /* Finished! :-) */ leave_drop_call: NOTE_VALUE(PAC, call_id_pair, htons(-1)); NOTE_VALUE(PNS, call_id_pair, htons(-1)); close(clientSocket); leave_clear_call: /* leave clientSocket and call_id_pair alone for bail() */ if (gre_fd != -1) close(gre_fd); gre_fd = -1; if (pty_fd != -1) close(pty_fd); pty_fd = -1; return; #ifdef init #undef init #endif }
/*** pptp_gre_copy ************************************************************/ void pptp_gre_copy(u_int16_t call_id, u_int16_t peer_call_id, int pty_fd, int gre_fd) { int max_fd; pptp_gre_call_id = call_id; pptp_gre_peer_call_id = peer_call_id; /* Pseudo-terminal already open. */ ack_sent = ack_recv = seq_sent = seq_recv = 0; /* weird select semantics */ max_fd = gre_fd; if (pty_fd > max_fd) max_fd = pty_fd; /* Dispatch loop */ for (;;) { /* until error happens on gre_fd or pty_fd */ struct timeval tv = {0, 0}; struct timeval *tvp; fd_set rfds; int retval; pqueue_t *head; int block_usecs = -1; /* wait forever */ /* watch terminal and socket for input */ FD_ZERO(&rfds); FD_SET(gre_fd, &rfds); FD_SET(pty_fd, &rfds); /* * if there are multiple pending ACKs, then do a minimal timeout; * else if there is a single pending ACK then timeout after 0,5 seconds; * else block until data is available. */ if (ack_sent != seq_recv) { if (ack_sent + 1 == seq_recv) /* u_int wrap-around safe */ block_usecs = 500000; else /* don't use zero, this will force a resceduling */ /* when calling select(), giving pppd a chance to */ /* run. */ block_usecs = 1; } /* otherwise block_usecs == -1, which means wait forever */ /* * If there is a packet in the queue, then don't wait longer than * the time remaining until it expires. */ head = pqueue_head(); if (head != NULL) { int expiry_time = pqueue_expiry_time(head); if (block_usecs == -1 || expiry_time < block_usecs) block_usecs = expiry_time; } if (block_usecs == -1) { tvp = NULL; } else { tvp = &tv; tv.tv_usec = block_usecs; tv.tv_sec = tv.tv_usec / 1000000; tv.tv_usec %= 1000000; } retval = select(max_fd + 1, &rfds, NULL, NULL, tvp); if (retval > 0 && FD_ISSET(pty_fd, &rfds)) { if (decaps_hdlc(pty_fd, encaps_gre, gre_fd) < 0) break; } else if (retval == 0 && ack_sent != seq_recv) { /* if outstanding ack */ /* send ack with no payload */ encaps_gre(gre_fd, NULL, 0); } if (retval > 0 && FD_ISSET(gre_fd, &rfds)) { if (decaps_gre (gre_fd, encaps_hdlc, pty_fd) < 0) break; } if (dequeue_gre (encaps_hdlc, pty_fd) < 0) break; } /* Close up when done. */ close(gre_fd); close(pty_fd); }