1 | /* |
2 | * kPPP: A pppd Front End for the KDE project |
3 | * |
4 | * $Id$ |
5 | * |
6 | * Copyright (C) 1997 Bernd Johannes Wuebben |
7 | * wuebben@math.cornell.edu |
8 | * |
9 | * This file was added by Harri Porten <porten@tu-harburg.de> |
10 | * |
11 | * |
12 | * This program is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU Library General Public |
14 | * License as published by the Free Software Foundation; either |
15 | * version 2 of the License, or (at your option) any later version. |
16 | * |
17 | * This program is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
20 | * Library General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU Library General Public |
23 | * License along with this program; if not, write to the Free |
24 | * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
25 | */ |
26 | |
27 | #include <errno.h> |
28 | #include <stdlib.h> |
29 | #include <fcntl.h> |
30 | #include <signal.h> |
31 | #include <sys/ioctl.h> |
32 | #include <setjmp.h> |
33 | #include <qregexp.h> |
34 | //Added by qt3to4: |
35 | #include <assert.h> |
36 | |
37 | #include "modem.h" |
38 | #include "pppdata.h" |
39 | #include "requester.h" |
40 | #include <klocale.h> |
41 | #include <kdebug.h> |
42 | #include <kcmdlineargs.h> |
43 | #include <config-kppp.h> |
44 | |
45 | static sigjmp_buf jmp_buffer; |
46 | |
47 | Modem *Modem::modem = 0; |
48 | |
49 | Modem::Modem() : |
50 | modemfd(-1), |
51 | sn(0L), |
52 | data_mode(false), |
53 | modem_is_locked(false) |
54 | { |
55 | assert(modem==0); |
56 | modem = this; |
57 | args = KCmdLineArgs::parsedArgs(); |
58 | } |
59 | |
60 | |
61 | Modem::~Modem() { |
62 | modem = 0; |
63 | } |
64 | |
65 | |
66 | speed_t Modem::modemspeed() { |
67 | // convert the string modem speed int the gpppdata object to a t_speed type |
68 | // to set the modem. The constants here should all be ifdef'd because |
69 | // other systems may not have them |
70 | int i = gpppdata.speed().toInt()/100; |
71 | |
72 | switch(i) { |
73 | case 24: |
74 | return B2400; |
75 | break; |
76 | case 96: |
77 | return B9600; |
78 | break; |
79 | case 192: |
80 | return B19200; |
81 | break; |
82 | case 384: |
83 | return B38400; |
84 | break; |
85 | #ifdef B57600 |
86 | case 576: |
87 | return B57600; |
88 | break; |
89 | #endif |
90 | |
91 | #ifdef B115200 |
92 | case 1152: |
93 | return B115200; |
94 | break; |
95 | #endif |
96 | |
97 | #ifdef B230400 |
98 | case 2304: |
99 | return B230400; |
100 | break; |
101 | #endif |
102 | |
103 | #ifdef B460800 |
104 | case 4608: |
105 | return B460800; |
106 | break; |
107 | #endif |
108 | |
109 | default: |
110 | return B38400; |
111 | break; |
112 | } |
113 | } |
114 | |
115 | |
116 | bool Modem::opentty() { |
117 | // int flags; |
118 | QString device = "" ; |
119 | if (args->isSet("dev" )) |
120 | device = args->getOption("dev" ); |
121 | else |
122 | device = gpppdata.modemDevice(); |
123 | kDebug() << "Opening Device: " << device; |
124 | |
125 | if((modemfd = Requester::rq->openModem(device))<0) { |
126 | errmsg = i18n("Unable to open modem." ); |
127 | return false; |
128 | } |
129 | |
130 | #if 0 |
131 | if(gpppdata.UseCDLine()) { |
132 | if(ioctl(modemfd, TIOCMGET, &flags) == -1) { |
133 | errmsg = i18n("Unable to detect state of CD line." ); |
134 | ::close(modemfd); |
135 | modemfd = -1; |
136 | return false; |
137 | } |
138 | if ((flags&TIOCM_CD) == 0) { |
139 | errmsg = i18n("The modem is not ready." ); |
140 | ::close(modemfd); |
141 | modemfd = -1; |
142 | return false; |
143 | } |
144 | } |
145 | #endif |
146 | |
147 | tcdrain (modemfd); |
148 | tcflush (modemfd, TCIOFLUSH); |
149 | |
150 | if(tcgetattr(modemfd, &tty) < 0){ |
151 | // this helps in some cases |
152 | tcsendbreak(modemfd, 0); |
153 | sleep(1); |
154 | if(tcgetattr(modemfd, &tty) < 0){ |
155 | errmsg = i18n("The modem is busy." ); |
156 | ::close(modemfd); |
157 | modemfd = -1; |
158 | return false; |
159 | } |
160 | } |
161 | |
162 | memset(&initial_tty,'\0',sizeof(initial_tty)); |
163 | |
164 | initial_tty = tty; |
165 | |
166 | tty.c_cc[VMIN] = 0; // nonblocking |
167 | tty.c_cc[VTIME] = 0; |
168 | tty.c_oflag = 0; |
169 | tty.c_lflag = 0; |
170 | |
171 | tty.c_cflag &= ~(CSIZE | CSTOPB | PARENB); |
172 | tty.c_cflag |= CS8 | CREAD; |
173 | tty.c_cflag |= CLOCAL; // ignore modem status lines |
174 | tty.c_iflag = IGNBRK | IGNPAR /* | ISTRIP */ ; |
175 | tty.c_lflag &= ~ICANON; // non-canonical mode |
176 | tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHOKE); |
177 | |
178 | |
179 | // the english/i18n mix below is ugly but we want to keep working |
180 | // after someone changed the code to use i18n'ed config values |
181 | QString flowCtrl = gpppdata.flowcontrol(); |
182 | if(flowCtrl != "None" && flowCtrl != i18n("None" )) { |
183 | if(flowCtrl == "CRTSCTS" || flowCtrl == i18n("Hardware [CRTSCTS]" )) { |
184 | tty.c_cflag |= CRTSCTS; |
185 | } |
186 | else { |
187 | tty.c_iflag |= IXON | IXOFF; |
188 | tty.c_cc[VSTOP] = 0x13; /* DC3 = XOFF = ^S */ |
189 | tty.c_cc[VSTART] = 0x11; /* DC1 = XON = ^Q */ |
190 | } |
191 | } |
192 | else { |
193 | tty.c_cflag &= ~CRTSCTS; |
194 | tty.c_iflag &= ~(IXON | IXOFF); |
195 | } |
196 | |
197 | cfsetospeed(&tty, modemspeed()); |
198 | cfsetispeed(&tty, modemspeed()); |
199 | |
200 | tcdrain(modemfd); |
201 | |
202 | if(tcsetattr(modemfd, TCSANOW, &tty) < 0){ |
203 | errmsg = i18n("The modem is busy." ); |
204 | ::close(modemfd); |
205 | modemfd=-1; |
206 | return false; |
207 | } |
208 | |
209 | errmsg = i18n("Modem Ready." ); |
210 | return true; |
211 | } |
212 | |
213 | |
214 | bool Modem::closetty() { |
215 | if(modemfd >=0 ) { |
216 | stop(); |
217 | /* discard data not read or transmitted */ |
218 | tcflush(modemfd, TCIOFLUSH); |
219 | |
220 | if(tcsetattr(modemfd, TCSANOW, &initial_tty) < 0){ |
221 | errmsg = i18n("Can not restore tty settings: tcsetattr()\n" ); |
222 | ::close(modemfd); |
223 | modemfd = -1; |
224 | return false; |
225 | } |
226 | ::close(modemfd); |
227 | modemfd = -1; |
228 | } |
229 | |
230 | return true; |
231 | } |
232 | |
233 | |
234 | void Modem::readtty(int) { |
235 | char buffer[200]; |
236 | unsigned char c; |
237 | int len; |
238 | |
239 | // read data in chunks of up to 200 bytes |
240 | if((len = ::read(modemfd, buffer, 200)) > 0) { |
241 | // split buffer into single characters for further processing |
242 | for(int i = 0; i < len; i++) { |
243 | c = buffer[i] & 0x7F; |
244 | emit charWaiting(c); |
245 | } |
246 | } |
247 | } |
248 | |
249 | |
250 | void Modem::notify(const QObject *receiver, const char *member) { |
251 | connect(this, SIGNAL(charWaiting(unsigned char)), receiver, member); |
252 | startNotifier(); |
253 | } |
254 | |
255 | |
256 | void Modem::stop() { |
257 | disconnect(SIGNAL(charWaiting(unsigned char))); |
258 | stopNotifier(); |
259 | } |
260 | |
261 | |
262 | void Modem::startNotifier() { |
263 | if(modemfd >= 0) { |
264 | if(sn == 0) { |
265 | sn = new QSocketNotifier(modemfd, QSocketNotifier::Read, this); |
266 | connect(sn, SIGNAL(activated(int)), SLOT(readtty(int))); |
267 | kDebug(5002) << "QSocketNotifier started!" ; |
268 | } else { |
269 | // Debug("QSocketNotifier re-enabled!"); |
270 | sn->setEnabled(true); |
271 | } |
272 | } |
273 | } |
274 | |
275 | |
276 | void Modem::stopNotifier() { |
277 | if(sn != 0) { |
278 | sn->setEnabled(false); |
279 | disconnect(sn); |
280 | delete sn; |
281 | sn = 0; |
282 | kDebug(5002) << "QSocketNotifier stopped!" ; |
283 | } |
284 | } |
285 | |
286 | |
287 | void Modem::flush() { |
288 | char c; |
289 | while(read(modemfd, &c, 1) == 1); |
290 | } |
291 | |
292 | |
293 | bool Modem::writeChar(unsigned char c) { |
294 | int s; |
295 | do { |
296 | s = write(modemfd, &c, 1); |
297 | if (s < 0) { |
298 | kError(5002) << "write() in Modem::writeChar failed" << endl; |
299 | return false; |
300 | } |
301 | } while(s == 0); |
302 | |
303 | return true; |
304 | } |
305 | |
306 | |
307 | bool Modem::writeLine(const char *buf) { |
308 | int len = strlen(buf); |
309 | char *b = new char[len+2]; |
310 | memcpy(b, buf, len); |
311 | // different modems seem to need different line terminations |
312 | QString term = gpppdata.enter(); |
313 | if(term == "LF" ) |
314 | b[len++]='\n'; |
315 | else if(term == "CR" ) |
316 | b[len++]='\r'; |
317 | else if(term == "CR/LF" ) { |
318 | b[len++]='\r'; |
319 | b[len++]='\n'; |
320 | } |
321 | int l = len; |
322 | while(l) { |
323 | int wr = write(modemfd, &b[len-l], l); |
324 | if(wr < 0) { |
325 | if (errno == EAGAIN) |
326 | continue; |
327 | // TODO do something meaningful with the error code (or ignore it |
328 | kError(5002) << "write() in Modem::writeLine failed" << endl; |
329 | delete[] b; |
330 | return false; |
331 | } |
332 | l -= wr; |
333 | } |
334 | delete[] b; |
335 | return true; |
336 | } |
337 | |
338 | |
339 | bool Modem::hangup() { |
340 | // this should really get the modem to hang up and go into command mode |
341 | // If anyone sees a fault in the following please let me know, since |
342 | // this is probably the most imporant snippet of code in the whole of |
343 | // kppp. If people complain about kppp being stuck, this piece of code |
344 | // is most likely the reason. |
345 | struct termios temptty; |
346 | |
347 | if(modemfd >= 0) { |
348 | |
349 | // is this Escape & HangupStr stuff really necessary ? (Harri) |
350 | |
351 | if (data_mode) escape_to_command_mode(); |
352 | |
353 | // Then hangup command |
354 | writeLine(gpppdata.modemHangupStr().toLocal8Bit()); |
355 | |
356 | usleep(gpppdata.modemInitDelay() * 10000); // 0.01 - 3.0 sec |
357 | |
358 | #ifndef DEBUG_WO_DIALING |
359 | if (sigsetjmp(jmp_buffer, 1) == 0) { |
360 | // set alarm in case tcsendbreak() hangs |
361 | signal(SIGALRM, alarm_handler); |
362 | alarm(2); |
363 | |
364 | tcsendbreak(modemfd, 0); |
365 | |
366 | alarm(0); |
367 | signal(SIGALRM, SIG_IGN); |
368 | } else { |
369 | // we reach this point if the alarm handler got called |
370 | closetty(); |
371 | close(modemfd); |
372 | modemfd = -1; |
373 | errmsg = i18n("The modem does not respond." ); |
374 | return false; |
375 | } |
376 | |
377 | #ifndef __SVR4 // drops DTR but doesn't set it afterwards again. not good for init. |
378 | tcgetattr(modemfd, &temptty); |
379 | cfsetospeed(&temptty, B0); |
380 | cfsetispeed(&temptty, B0); |
381 | tcsetattr(modemfd, TCSAFLUSH, &temptty); |
382 | #else |
383 | int modemstat; |
384 | ioctl(modemfd, TIOCMGET, &modemstat); |
385 | modemstat &= ~TIOCM_DTR; |
386 | ioctl(modemfd, TIOCMSET, &modemstat); |
387 | ioctl(modemfd, TIOCMGET, &modemstat); |
388 | modemstat |= TIOCM_DTR; |
389 | ioctl(modemfd, TIOCMSET, &modemstat); |
390 | #endif |
391 | |
392 | usleep(gpppdata.modemInitDelay() * 10000); // 0.01 - 3.0 secs |
393 | |
394 | cfsetospeed(&temptty, modemspeed()); |
395 | cfsetispeed(&temptty, modemspeed()); |
396 | tcsetattr(modemfd, TCSAFLUSH, &temptty); |
397 | #endif |
398 | return true; |
399 | } else |
400 | return false; |
401 | } |
402 | |
403 | |
404 | void Modem::escape_to_command_mode() { |
405 | // Send Properly bracketed escape code to put the modem back into command state. |
406 | // A modem will accept AT commands only when it is in command state. |
407 | // When a modem sends the host the CONNECT string, that signals |
408 | // that the modem is now in the connect state (no long accepts AT commands.) |
409 | // Need to send properly timed escape sequence to put modem in command state. |
410 | // Escape codes and guard times are controlled by S2 and S12 values. |
411 | // |
412 | tcflush(modemfd, TCIOFLUSH); |
413 | |
414 | // +3 because quiet time must be greater than guard time. |
415 | usleep((gpppdata.modemEscapeGuardTime()+3)*20000); |
416 | QByteArray tmp = gpppdata.modemEscapeStr().toLocal8Bit(); |
417 | write(modemfd, tmp.data(), tmp.length()); |
418 | tcflush(modemfd, TCIOFLUSH); |
419 | usleep((gpppdata.modemEscapeGuardTime()+3)*20000); |
420 | |
421 | data_mode = false; |
422 | } |
423 | |
424 | |
425 | const QString Modem::modemMessage() { |
426 | return errmsg; |
427 | } |
428 | |
429 | |
430 | QString Modem::parseModemSpeed(const QString &s) { |
431 | // this is a small (and bad) parser for modem speeds |
432 | int rx = -1; |
433 | int tx = -1; |
434 | int i; |
435 | QString result; |
436 | |
437 | kDebug(5002) << "Modem reported result string: " << s; |
438 | |
439 | const int RXMAX = 7; |
440 | const int TXMAX = 2; |
441 | QRegExp rrx[RXMAX] = { |
442 | QRegExp("[0-9]+[:/ ]RX" , Qt::CaseInsensitive), |
443 | QRegExp("[0-9]+RX" , Qt::CaseInsensitive), |
444 | QRegExp("[/: -][0-9]+[/: ]" , Qt::CaseInsensitive), |
445 | QRegExp("[/: -][0-9]+$" , Qt::CaseInsensitive), |
446 | QRegExp("CARRIER [^0-9]*[0-9]+" , Qt::CaseInsensitive), |
447 | QRegExp("CONNECT [^0-9]*[0-9]+" , Qt::CaseInsensitive), |
448 | QRegExp("[0-9]+" ) // panic mode |
449 | }; |
450 | |
451 | QRegExp trx[TXMAX] = { |
452 | QRegExp("[0-9]+[:/ ]TX" , Qt::CaseInsensitive), |
453 | QRegExp("[0-9]+TX" , Qt::CaseInsensitive) |
454 | }; |
455 | |
456 | for(i = 0; i < RXMAX; i++) { |
457 | int len, idx, result; |
458 | if((idx = rrx[i].indexIn(s)) > -1) { |
459 | len = rrx[i].matchedLength(); |
460 | |
461 | // |
462 | // rrx[i] has been matched, idx contains the start of the match |
463 | // and len contains how long the match is. Extract the match. |
464 | // |
465 | QString sub = s.mid(idx, len); |
466 | |
467 | // |
468 | // Now extract the digits only from the match, which will |
469 | // then be converted to an int. |
470 | // |
471 | if ((idx = rrx[RXMAX-1].indexIn( sub )) > -1) { |
472 | len = rrx[RXMAX-1].matchedLength(); |
473 | sub = sub.mid(idx, len); |
474 | result = sub.toInt(); |
475 | if(result > 0) { |
476 | rx = result; |
477 | break; |
478 | } |
479 | } |
480 | } |
481 | } |
482 | |
483 | for(i = 0; i < TXMAX; i++) { |
484 | int len, idx, result; |
485 | if((idx = trx[i].indexIn(s)) > -1) { |
486 | len = trx[i].matchedLength(); |
487 | |
488 | // |
489 | // trx[i] has been matched, idx contains the start of the match |
490 | // and len contains how long the match is. Extract the match. |
491 | // |
492 | QString sub = s.mid(idx, len); |
493 | |
494 | // |
495 | // Now extract the digits only from the match, which will then |
496 | // be converted to an int. |
497 | // |
498 | if((idx = rrx[RXMAX-1].indexIn(sub)) > -1) { |
499 | len = rrx[RXMAX-1].matchedLength(); |
500 | sub = sub.mid(idx, len); |
501 | result = sub.toInt(); |
502 | if(result > 0) { |
503 | tx = result; |
504 | break; |
505 | } |
506 | } |
507 | } |
508 | } |
509 | |
510 | if(rx == -1 && tx == -1) |
511 | result = i18n("Unknown speed" ); |
512 | else if(tx == -1) |
513 | result.setNum(rx); |
514 | else if(rx == -1) // should not happen |
515 | result.setNum(tx); |
516 | else |
517 | result.sprintf("%d/%d" , rx, tx); |
518 | |
519 | kDebug(5002) << "The parsed result is: " << result; |
520 | |
521 | return result; |
522 | } |
523 | |
524 | |
525 | // Lock modem device. Returns 0 on success 1 if the modem is locked and -1 if |
526 | // a lock file can't be created ( permission problem ) |
527 | int Modem::lockdevice() { |
528 | int fd; |
529 | char newlock[80]="" ; // safe |
530 | |
531 | if(!gpppdata.modemLockFile()) { |
532 | kDebug(5002) << "The user doesn't want a lockfile." ; |
533 | return 0; |
534 | } |
535 | |
536 | if (modem_is_locked) |
537 | return 1; |
538 | |
539 | QString device = "" ; |
540 | if (args->isSet("dev" )) |
541 | device = args->getOption("dev" ); |
542 | else |
543 | device = gpppdata.modemDevice(); |
544 | |
545 | QString lockfile = LOCK_DIR"/LCK.." ; |
546 | lockfile += device.mid(5); // append everything after /dev/ |
547 | |
548 | if(access(QFile::encodeName(lockfile), F_OK) == 0) { |
549 | if ((fd = Requester::rq->openLockfile(QFile::encodeName(lockfile), O_RDONLY)) >= 0) { |
550 | // Mario: it's not necessary to read more than lets say 32 bytes. If |
551 | // file has more than 32 bytes, skip the rest |
552 | char oldlock[33]; // safe |
553 | int sz = read(fd, &oldlock, 32); |
554 | close (fd); |
555 | if (sz <= 0) |
556 | return 1; |
557 | oldlock[sz] = '\0'; |
558 | |
559 | kDebug(5002) << "Device is locked by: " << &oldlock; |
560 | |
561 | int oldpid; |
562 | int match = sscanf(oldlock, "%d" , &oldpid); |
563 | |
564 | // found a pid in lockfile ? |
565 | if (match < 1 || oldpid <= 0) |
566 | return 1; |
567 | |
568 | // check if process exists |
569 | if (kill((pid_t)oldpid, 0) == 0 || errno != ESRCH) |
570 | return 1; |
571 | |
572 | kDebug(5002) << "lockfile is stale" ; |
573 | } |
574 | } |
575 | |
576 | fd = Requester::rq->openLockfile(device, |
577 | O_WRONLY|O_TRUNC|O_CREAT); |
578 | if(fd >= 0) { |
579 | sprintf(newlock,"%010d\n" , getpid()); |
580 | kDebug(5002) << "Locking Device: " << newlock; |
581 | |
582 | write(fd, newlock, strlen(newlock)); |
583 | close(fd); |
584 | modem_is_locked=true; |
585 | |
586 | return 0; |
587 | } |
588 | |
589 | return -1; |
590 | |
591 | } |
592 | |
593 | |
594 | // UnLock modem device |
595 | void Modem::unlockdevice() { |
596 | if (modem_is_locked) { |
597 | kDebug(5002) << "UnLocking Modem Device" ; |
598 | Requester::rq->removeLockfile(); |
599 | modem_is_locked=false; |
600 | } |
601 | } |
602 | |
603 | void alarm_handler(int) { |
604 | // fprintf(stderr, "alarm_handler(): Received SIGALRM\n"); |
605 | |
606 | // jump |
607 | siglongjmp(jmp_buffer, 1); |
608 | } |
609 | |
610 | #include "modem.moc" |
611 | |