int main(int argc, char **argv) { char pppLocal[16]; /* local IP to pass to pppd */ char pppRemote[16]; /* remote IP address to pass to pppd */ struct sockaddr_in addr; /* client address */ socklen_t addrlen; int arg = 1; int flags; struct in_addr inetaddrs[2]; char *pppaddrs[2] = { pppLocal, pppRemote }; gargc = argc; gargv = argv; /* fail if argument count invalid */ if (argc < 7) { fprintf(stderr, "pptpctrl: insufficient arguments, see man pptpctrl\n"); exit(2); } /* open a connection to the syslog daemon */ openlog("pptpd", LOG_PID, PPTP_FACILITY); /* autoreap if supported */ signal(SIGCHLD, SIG_IGN); /* note: update pptpctrl.8 if the argument list format is changed */ GETARG_INT(pptpctrl_debug); GETARG_INT(noipparam); GETARG_VALUE(pppdxfig); GETARG_VALUE(speed); GETARG_VALUE(pppLocal); GETARG_VALUE(pppRemote); if (arg < argc) GETARG_INT(unique_call_id); if (arg < argc) GETARG_STRING(ppp_binary); if (arg < argc) GETARG_INT(pptp_logwtmp); if (pptpctrl_debug) { if (*pppLocal) syslog(LOG_DEBUG, "CTRL: local address = %s", pppLocal); if (*pppRemote) syslog(LOG_DEBUG, "CTRL: remote address = %s", pppRemote); if (*speed) syslog(LOG_DEBUG, "CTRL: pppd speed = %s", speed); if (*pppdxfig) syslog(LOG_DEBUG, "CTRL: pppd options file = %s", pppdxfig); } addrlen = sizeof(addr); if (getsockname(clientSocket, (struct sockaddr *) &addr, &addrlen) != 0) { syslog(LOG_ERR, "CTRL: getsockname() failed"); syslog_perror("getsockname"); close(clientSocket); bail(0); /* NORETURN */ } inetaddrs[0] = addr.sin_addr; addrlen = sizeof(addr); if (getpeername(clientSocket, (struct sockaddr *) &addr, &addrlen) != 0) { syslog(LOG_ERR, "CTRL: getpeername() failed"); syslog_perror("getpeername"); close(clientSocket); bail(0); /* NORETURN */ } inetaddrs[1] = addr.sin_addr; /* Set non-blocking */ if ((flags = fcntl(clientSocket, F_GETFL, arg /* ignored */)) == -1 || fcntl(clientSocket, F_SETFL, flags|OUR_NB_MODE) == -1) { syslog(LOG_ERR, "CTRL: Failed to set client socket non-blocking"); syslog_perror("fcntl"); close(clientSocket); bail(0); /* NORETURN */ } /* Fiddle with argv */ my_setproctitle(gargc, gargv, "pptpd [%s]%20c", inet_ntoa(addr.sin_addr), ' '); /* be ready for a grisly death */ sigpipe_create(); sigpipe_assign(SIGTERM); NOTE_VALUE(PAC, call_id_pair, htons(-1)); NOTE_VALUE(PNS, call_id_pair, htons(-1)); syslog(LOG_INFO, "CTRL: Client %s control connection started", inet_ntoa(addr.sin_addr)); pptp_handle_ctrl_connection(pppaddrs, inetaddrs); syslog(LOG_DEBUG, "CTRL: Reaping child PPP[%i]", pppfork); if (pppfork > 0) waitpid(pppfork, NULL, 0); syslog(LOG_INFO, "CTRL: Client %s control connection finished", inet_ntoa(addr.sin_addr)); bail(0); /* NORETURN */ return 1; /* make gcc happy */ }
/* * 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_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 }