/* * 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 }
/* * This is the custom exit() for this program. * * Updated to also be the default SIGTERM handler, and if * the link is going down for unnatural reasons, we will close it * right now, it's only been tested for win98, other tests would be nice * -tmk */ static void bail(int sigraised) { if (sigraised) syslog(LOG_INFO, "CTRL: Exiting on signal %d", sigraised); /* send a disconnect to the other end */ /* ignore any errors */ if (GET_VALUE(PAC, call_id_pair) != htons(-1)) { fd_set connSet; /* fd_set for select() */ struct timeval tv; /* time to wait for reply */ unsigned char packet[PPTP_MAX_CTRL_PCKT_SIZE]; unsigned char rply_packet[PPTP_MAX_CTRL_PCKT_SIZE]; ssize_t rply_size; /* reply packet size */ int pkt; int retry = 0; if (pptpctrl_debug) syslog(LOG_DEBUG, "CTRL: Exiting with active call"); make_call_admin_shutdown(rply_packet, &rply_size); if(send_pptp_packet(clientSocket, rply_packet, rply_size) < 0) goto skip; make_stop_ctrl_req(rply_packet, &rply_size); if(send_pptp_packet(clientSocket, rply_packet, rply_size) < 0) goto skip; FD_ZERO(&connSet); FD_SET(clientSocket, &connSet); tv.tv_sec = 5; /* wait 5 secs for a reply then quit */ tv.tv_usec = 0; /* Wait for STOP CTRL CONN RQST or RPLY */ while (select(clientSocket + 1, &connSet, NULL, NULL, &tv) == 1) { switch((pkt = read_pptp_packet(clientSocket, packet, rply_packet, &rply_size))) { case STOP_CTRL_CONN_RQST: send_pptp_packet(clientSocket, rply_packet, rply_size); goto skip; case CALL_CLR_RQST: syslog(LOG_WARNING, "CTRL: Got call clear request after call manually shutdown - buggy client"); break; case STOP_CTRL_CONN_RPLY: goto skip; case -1: syslog(LOG_WARNING, "CTRL: Retryable error in disconnect sequence"); retry++; break; case 0: syslog(LOG_WARNING, "CTRL: Fatal error reading control message in disconnect sequence"); goto skip; default: syslog(LOG_WARNING, "CTRL: Unexpected control message %d in disconnect sequence", pkt); retry++; break; } tv.tv_sec = 5; /* wait 5 secs for another reply then quit */ tv.tv_usec = 0; if (retry > 100) { syslog(LOG_WARNING, "CTRL: Too many retries (%d) - giving up", retry); break; } } skip: close(clientSocket); } if (pptpctrl_debug) syslog(LOG_DEBUG, "CTRL: Exiting now"); }
/* * 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 sig_fd = sigpipe_fd(); /* Signal pipe descriptor */ unsigned char packet[PPTP_MAX_CTRL_PCKT_SIZE]; unsigned char rply_packet[PPTP_MAX_CTRL_PCKT_SIZE]; struct sockaddr_pppox dst_addr; for (;;) { FD_ZERO(&fds); FD_SET(sig_fd, &fds); FD_SET(clientSocket, &fds); /* set timeout */ 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 (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); } /* 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)"); 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)"); /* 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); pptp_timeout*=1000; if (setsockopt(pptp_sock,0,PPTP_SO_TIMEOUT,&pptp_timeout,sizeof(pptp_timeout))) syslog(LOG_WARNING,"CTRL: failed to setsockopt SO_TIMEOUT\n"); dst_addr.sa_family=AF_PPPOX; dst_addr.sa_protocol=PX_PROTO_PPTP; dst_addr.sa_addr.pptp.call_id=htons(((struct pptp_out_call_rply *) (rply_packet))->call_id_peer); dst_addr.sa_addr.pptp.sin_addr=inetaddrs[1]; if (connect(pptp_sock,(struct sockaddr*)&dst_addr,sizeof(dst_addr))){ syslog(LOG_ERR,"CTRL: failed to connect PPTP socket (%s)\n",strerror(errno)); exit(-1); break; } /* 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)"); startCall(pppaddrs, inetaddrs); if (PPP_WAIT) { switch(ioctl(pptp_sock,PPPTPIOWFP,PPP_WAIT)){ case -1: syslog(LOG_ERR, "CTRL: waiting for first packet failed, ignoring"); break; case 0: syslog(LOG_ERR, "CTRL: timeout waiting for first packet"); break; } } close(pptp_sock); pptp_sock=-1; 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: return; #ifdef init #undef init #endif }