Source code: jhfc/HylaFaxConnectionManager.java
1 /*
2 * $Id: NetCon.java,v 0.12 1998/11/23 14:19:22 choeger Exp choeger $
3 *
4 * Die Klasse NetCon
5 * (c) 1997 S.u.S.E. GmbH
6 * Autor: Carsten Hoeger
7 *
8 * Zustaendig fuer die Kommunikation mit dem HylaFAX
9 * Protokollserver. Saemtliche Ausnahmen werden in der naechst
10 * hoeheren Instanz verarbeitet
11 *
12 * Konstruktor:
13 * public NetCon(int port, String host, boolean debug)
14 *
15 * int port : Portnummer des HylaFax Servers (default 4559)
16 * String host : Hostname des Fax-Servers
17 * boolean debug : Debug-Flag
18 */
19 package jhfc;
20
21
22 import java.io.*;
23 import java.net.*;
24 import java.util.StringTokenizer;
25 import java.util.Vector;
26 import java.util.zip.*;
27
28 public class HylaFaxConnectionManager
29 {
30 private String hostname;
31 private int port;
32
33 private boolean debug;
34
35 // Reply Codes des ftp Protokolles (rfc959)
36 public static final int CONNECTION_ALREADY_OPEN = 125;
37 public static final int ABOUT_TO_OPEN_DATACON = 150;
38 public static final int COMMAND_OK = 200;
39 public static final int COM_NOT_IMPL_SUPERFLUOUS = 202;
40 public static final int SYSTEM_STATUS = 211;
41 public static final int DIRECTORY_STATUS = 212;
42 public static final int FILE_STATUS = 213;
43 public static final int HELP_MESSAGE = 214;
44 public static final int CLOSING_CONNECTION = 221;
45 public static final int CLOSING_DATA_CONNECTION = 226;
46 public static final int USER_LOGGED_IN = 230;
47 public static final int FILE_ACTION_OK = 250;
48 public static final int NEEDS_PASSWD = 331;
49 public static final int NEEDS_ACCOUNT = 332;
50 public static final int SERVICE_NOT_AVAILABLE = 421;
51 public static final int NO_DATA_CONNECTION = 425;
52 public static final int CONNECTION_CLOSED = 426;
53 public static final int FILE_ACTION_NOT_TAKEN = 450;
54 public static final int ACTION_ABORTED_ERROR = 451;
55 public static final int ACTION_NOT_TAKEN_SPACE = 452;
56 public static final int FAILED_TO_KILL_JOB = 460;
57 public static final int SYNTAX_ERROR_COMMAND = 500;
58 public static final int SYNTAX_ERROR_PARAMETER = 501;
59 public static final int COMMAND_NOT_IMPLEMENTED = 502;
60 public static final int BAD_COMMAND_SEQUENCE = 503;
61 public static final int OPERATION_NOT_PERMITTET = 504;
62 public static final int NOT_LOGGED_IN = 530;
63 public static final int NEED_ACC_FOR_STORING = 532;
64 public static final int ACTION_NOT_TAKEN = 550;
65 public static final int ACTION_ABORTED_PAGETYPE = 551;
66 public static final int FILE_ACTION_ABORTED = 552;
67 public static final int ACTION_NOT_TAKEN_NAME = 553;
68
69 // Eigene Reply-Codes
70 public static final int LOST_CONNECTION = -1;
71 public static final int EMPTY_REPLY_STRING = -2;
72
73 private Socket socket = null;
74 private BufferedReader clntIn = null;
75 private PrintWriter clntOut = null;
76
77 protected static final int NONE = 0;
78 protected static final int RECEIVE = 1;
79 protected static final int SEND = 2;
80 private static int typeOfThread = NONE;
81 private static String errText = "";
82
83 // Konstruktor
84 public HylaFaxConnectionManager
85 (
86 String hostname,
87 int port,
88 boolean debug
89 )
90 {
91 this.hostname=hostname;
92 this.port=port;
93 this.debug=debug;
94 debug=true;
95 }
96
97
98 public void openConnection
99 (
100 )
101 throws java.io.IOException,
102 jhfc.ProtocollException
103 {
104 String line;
105
106 // Socket erzeugen
107 System.out.println("opening socket to "+hostname+" over port "+port+"...");
108 socket = new Socket(hostname, port);
109
110 if(debug)
111 {
112 System.out.println("socket open: " + socket.getInetAddress() +
113 ":" + socket.getPort());
114 }
115
116 // I/O Streams erzeugen
117 clntIn=new BufferedReader(new InputStreamReader(socket.getInputStream()));
118 clntOut=new PrintWriter(socket.getOutputStream());
119
120
121 check(clntIn.readLine());
122 }
123
124
125
126 public int login
127 (
128 String username
129 )
130 throws java.io.IOException,
131 jhfc.ProtocollException
132 {
133 clntOut.print("USER " + username + "\n");
134 clntOut.flush();
135
136 // Reply-Code zurueckgeben
137 return check(clntIn.readLine());
138 }
139
140
141 private String makePORT(byte iaddr[], int port) {
142 // unteres byte
143 byte a = (byte)( port & 0xff );
144 // oberes byte
145 byte b = (byte)( ( port & 0xff00 ) >> 8 );
146
147 // Zusammensetzen des Strings
148 return new String((0xff & iaddr[0]) + "," + (0xff & iaddr[1]) + "," +
149 (0xff & iaddr[2]) + "," + (0xff & iaddr[3]) + "," +
150 (0xff & b) + "," + (0xff & a));
151 // Die UND-Verknuepfung mit 0xff ist notwendig, um das
152 // Vorzeichen-Bit auszumaskieren
153 // byte in Java = -128 - +127
154 }
155
156
157 protected int check
158 (
159 String str
160 )
161 throws ProtocollException
162 {
163 /*
164 * check prueft die Antworten des Faxservers und reagiert
165 * folgendermassen:
166 *
167 * - gibt die Nummer des Reply-Codes zurueck, wenn alles
168 * in Ordnung ist
169 * - erzeugt ein Ausnahmeobjekt vom Typ PROTOException
170 * wenn ein fataler Fehler auftrat
171 * - gibt 0 zurück, wenn str kein Reply-Code ist
172 */
173
174 // if( debug )
175 // System.out.println("NetCon.check: str='"+str+"'");
176
177 String delim;
178 String Message = new String("");
179 int ret_val;
180
181 /* Wenn der Protokollserver beendet wird, waerend eine Verbindung
182 * bestand, wird ein null-String geliefert */
183 if( str == null )
184 {
185 System.out.println(getClass().getName()+".check(): empty Reply String!!!");
186 Thread.dumpStack();
187 throw new ProtocollException(getClass().getName()+".check(): empty Reply String!!!",
188 EMPTY_REPLY_STRING);
189 }
190
191 if( str.charAt(3) == '-' )
192 delim = new String("-");
193 else
194 delim = new String(" ");
195
196 StringTokenizer st = new StringTokenizer(str,delim);
197
198 // Wenn str ein normaler String ist, gib 0 zurueck
199 try {
200 ret_val = new Integer(st.nextToken()).intValue();
201 }
202 catch (NumberFormatException e) {
203 ret_val=0;
204 }
205
206 for(int i=st.countTokens(); i>0; i--) {
207 Message += st.nextToken() + " ";
208 }
209
210 // if( debug )
211 // System.out.println("NetCon.check: " + Message);
212
213 // Die folgenden Reply-Codes erzeugen ein Ausnahmeobject
214 if( ret_val == SERVICE_NOT_AVAILABLE ||
215 ret_val == NO_DATA_CONNECTION ||
216 ret_val == CONNECTION_CLOSED ||
217 ret_val == FILE_ACTION_NOT_TAKEN ||
218 ret_val == ACTION_ABORTED_ERROR ||
219 ret_val == ACTION_NOT_TAKEN_SPACE ||
220 ret_val == SYNTAX_ERROR_COMMAND ||
221 ret_val == SYNTAX_ERROR_PARAMETER ||
222 ret_val == COMMAND_NOT_IMPLEMENTED ||
223 ret_val == BAD_COMMAND_SEQUENCE ||
224 ret_val == OPERATION_NOT_PERMITTET ||
225 ret_val == NOT_LOGGED_IN ||
226 ret_val == NEED_ACC_FOR_STORING ||
227 ret_val == ACTION_NOT_TAKEN ||
228 ret_val == ACTION_ABORTED_PAGETYPE ||
229 ret_val == FILE_ACTION_ABORTED ||
230 ret_val == FAILED_TO_KILL_JOB ||
231 ret_val == ACTION_NOT_TAKEN_NAME )
232 {
233 System.out.println(getClass().getName()+".check(): "+Message+" ret_val="+ret_val);
234 // Thread.dumpStack();
235 throw new ProtocollException(Message, ret_val);
236 }
237 else
238 return ret_val;
239 }
240
241
242
243 public void logout
244 (
245 )
246 {
247 try
248 {
249 clntOut.print("QUIT" + "\n");
250 clntOut.flush();
251
252 check(clntIn.readLine());
253 }
254 catch (java.io.IOException ex)
255 {
256 System.out.println(getClass().getName()+"(): "+ex+" "+ex.getMessage());
257 Thread.dumpStack();
258 }
259 catch (jhfc.ProtocollException ex)
260 {
261 System.out.println(getClass().getName()+"(): "+ex+" "+ex.getMessage());
262 Thread.dumpStack();
263 }
264 }
265
266 // Verbindung schliessen
267 public void closeConnection
268 (
269 )
270 {
271 if(socket!=null)
272 {
273 try
274 {
275 clntIn.close();
276 clntOut.close();
277 socket.close();
278 System.out.println("socket to "+hostname+" closed");
279 }
280 catch (java.io.IOException ex)
281 {
282 System.out.println(getClass().getName()+"(): "+ex+" "+ex.getMessage());
283 Thread.dumpStack();
284 }
285 }
286 }
287
288 public String sendbuffer(char buf[]) throws Exception {
289 byte bytebuf[] = new byte[buf.length];
290
291 for(int i=0; i<buf.length; i++)
292 bytebuf[i] = (byte)buf[i];
293
294 return sendbuffer(bytebuf);
295 }
296
297 public String sendbuffer
298 (
299 byte buf[]
300 )
301 throws java.io.IOException,
302 jhfc.ProtocollException
303 {
304 // Diese Funktion sendet das Byte-Array buf an den Server
305 SendServ sndsrv;
306 String pstr;
307 byte iaddr[];
308
309 Deflater df = new Deflater(9, false);
310
311 ByteArrayOutputStream baos = new ByteArrayOutputStream();
312
313 DeflaterOutputStream dos =
314 new DeflaterOutputStream(baos, df, buf.length);
315
316 dos.write(buf, 0, buf.length);
317 dos.close();
318
319 //System.out.write(baos.toByteArray(), 0, baos.toByteArray().length);
320
321 //System.exit(0);
322
323 // Erzeugen des SendServ-Threads
324 //sndsrv = new SendServ(buf, debug);
325 sndsrv = new SendServ(baos.toByteArray(), debug);
326
327 iaddr = getInetAddr();
328 pstr = makePORT(iaddr, sndsrv.port);
329
330 clntOut.print("TYPE I" + "\n"); // Binaer
331 clntOut.flush();
332 check(clntIn.readLine());
333
334 clntOut.print("MODE Z" + "\n"); // ZIP
335 //clntOut.print("MODE S" + "\n"); // Stream
336 clntOut.flush();
337 check(clntIn.readLine());
338
339 clntOut.print("PORT " + pstr + "\n");
340 clntOut.flush();
341 check(clntIn.readLine());
342
343 clntOut.print("STOT" + "\n");
344 clntOut.flush();
345 String line = clntIn.readLine();
346 check(line);
347
348 if( debug )
349 System.out.println("NetCon.sendbuffer: Jetzt senden!!!");
350
351 // Auf Beendigung des Threads warten
352 try {
353 sndsrv.join();
354 }
355 catch (InterruptedException e) {};
356
357 StringTokenizer st = new StringTokenizer(line," ");
358 st.nextToken();
359 st.nextToken();
360 String filename = st.nextToken();
361 check(clntIn.readLine());
362
363 // Zurueckgegeben wird der Dateiname, unter dem der Server
364 // den Buffer gespeichert hat
365 return filename;
366 }
367
368 public byte[] getReceived
369 (
370 String name
371 )
372 throws java.io.IOException,
373 jhfc.ProtocollException
374 {
375 // Diese Funktion gibt in einem Byte-Array die angeforderte
376 // Datei name zurueck.
377
378 String pstr; // String für port
379 byte iaddr[]; // byte-array für Internetadresse
380 RecvServ recsrv; // server-thread-object
381
382 // Thread erzeugen
383 recsrv = new RecvServ(debug);
384
385 iaddr = getInetAddr();
386 pstr = makePORT(iaddr, recsrv.port);
387
388 if( debug )
389 System.out.println("NetCon.getReceived: " + pstr);
390
391 clntOut.print("TYPE I" + "\n");
392 clntOut.flush();
393 check(clntIn.readLine());
394
395 clntOut.print("MODE S" + "\n");
396 //clntOut.print("MODE Z" + "\n");
397 clntOut.flush();
398 check(clntIn.readLine());
399
400 // clntOut.print("CWD recvq" + "\n"); // in das recvq Verzeichnis wechseln
401 // clntOut.flush();
402 // check(clntIn.readLine());
403
404 clntOut.print("PORT " + pstr + "\n");
405 clntOut.flush();
406 check(clntIn.readLine());
407
408 clntOut.print("RETR " + name + "\n");
409 clntOut.flush();
410 check(clntIn.readLine());
411 check(clntIn.readLine());
412
413 // Auf Beendigung des Threads warten
414 try {
415 recsrv.join();
416 }
417 catch (InterruptedException e) {};
418
419 clntOut.print("CWD" + "\n"); // zurueck nach Serverroot
420 clntOut.flush();
421 check(clntIn.readLine());
422
423 if( debug )
424 System.out.println("NetCon.getReceived(): " + recsrv.data.length);
425
426 //ByteArrayInputStream is = new ByteArrayInputStream(recsrv.data);
427
428 //InflaterInputStream iis = new InflaterInputStream(is);
429
430 return recsrv.data;
431 }
432
433 public String list
434 (
435 String what
436 )
437 throws java.io.IOException,
438 jhfc.ProtocollException
439 {
440 // Diese Funktion gibt den Inhalt des mit what angegebenen
441 // Verzeichnisses als String zurueck.
442
443 String pstr; // String für port
444 String line; // String für linebuffer
445 byte iaddr[]; // byte-array für Internetadresse
446 RecvServ recsrv; // server-thread-object
447
448 // Thread erzeugen
449 recsrv = new RecvServ(debug);
450
451 iaddr = getInetAddr();
452 pstr = makePORT(iaddr, recsrv.port);
453
454 if( debug )
455 System.out.println("NetCon.infoS: " + pstr);
456
457 clntOut.print("PORT " + pstr + "\n");
458 clntOut.flush();
459 check(clntIn.readLine());
460
461 clntOut.print("LIST " + what + "\n");
462 clntOut.flush();
463
464
465 // Auf Beendigung des Threads warten
466 try {
467 recsrv.join();
468 }
469 catch (InterruptedException e) {};
470
471 if( typeOfThread != NONE ) {
472 // throw new ProtocollException(errText, LOST_CONNECTION);
473 throw new java.io.IOException(errText);
474 }
475
476 if( check(clntIn.readLine()) == ABOUT_TO_OPEN_DATACON ) {
477 check(clntIn.readLine());
478 } else {
479 // throw new ProtocollException("NetCon.infoS: No Data from Faxserver",1);
480 throw new java.io.IOException("NetCon.infoS: No Data from Faxserver");
481 }
482
483 // // Auf Beendigung des Threads warten
484 // try {
485 // recsrv.join();
486 // }
487 // catch (InterruptedException e) {};
488
489 int cnt; // Anzahl der Zeichen zaehlen
490 for(cnt=0; cnt<recsrv.data.length; cnt++)
491 if( recsrv.data[cnt] == 0 ) break;
492
493 //hibyte - the top 8 bits of each 16-bit Unicode character
494 return new String(recsrv.data,0,cnt);
495 }
496
497
498 public String command
499 (
500 String what
501 )
502 throws java.io.IOException,
503 jhfc.ProtocollException
504 {
505 /*
506 * Sendet ein Kommando an den Faxserver und
507 * gibt die Antwort zurueck oder erzeugt ein
508 * Ausnahmeobjekt
509 */
510
511 byte buf[] = new byte[80];
512 byte data[];
513 String response;
514 String line;
515
516 clntOut.print(what + "\n");
517 clntOut.flush();
518
519 response=new String(clntIn.readLine());
520 int erg = check(response);
521
522 if(debug==true)
523 {
524 // System.out.println(getClass().getName()+": erg="+erg+" what='"+what+"' "+response.charAt(3));
525 }
526
527 // SYSTEM_STATUS und HELP_MESSAGE sind laenger als
528 // eine Zeile
529 if((erg==SYSTEM_STATUS)||
530 (erg==HELP_MESSAGE)||
531 ((erg==FILE_STATUS)&&(response.charAt(3)=='-')))
532 {
533 while(true)
534 {
535 line = clntIn.readLine();
536 if( check(line) == erg )
537 {
538 break;
539 }
540 response += line + "\n";
541 }
542 }
543
544 return response;
545 }
546
547
548
549
550 private byte[] getInetAddr
551 (
552 )
553 {
554 byte iaddr[];
555
556 try
557 {
558 iaddr = socket.getInetAddress().getLocalHost().getAddress();
559 }
560 catch (java.net.UnknownHostException ex)
561 {
562 System.out.println(getClass().getName()+".check(): empty Reply String!!!");
563 Thread.dumpStack();
564 System.exit(-1);
565 return(null);
566 }
567 return(iaddr);
568 }
569
570
571 protected static void fail
572 (
573 String msg,
574 Exception e,
575 int which
576 )
577 {
578 // Wird vom RecServ aufgerufen, wenn im Thread ein Fehler auftritt
579 System.err.println(msg + ": " + e);
580
581 typeOfThread = which;
582 errText = msg + ": " + e;
583 }
584 }
585
586 /*
587 * Die Klasse RecServ ist abgeleitet von der Klasse Thread.
588 * Sie empfaengt Daten vom Protokoll Server
589 */
590
591 class RecvServ extends Thread {
592 protected int port;
593 protected byte data[];
594
595 private final static int timeout = 20; // in seconds
596 private ServerSocket srv;
597 private Socket srv_clnt;
598 private boolean debug;
599
600
601 public RecvServ
602 (
603 boolean debug
604 )
605 throws java.io.IOException
606 {
607 this.debug = debug;
608
609 // naechsten freien Port benutzen
610 srv = new ServerSocket(0, timeout);
611 port = srv.getLocalPort();
612 if( debug )
613 System.out.println("RecvServ: port=" + port);
614 this.start();
615 }
616
617 // thread body
618 public void run() {
619 DataInputStream in;
620 byte buf[] = new byte[1024];
621
622 try {
623 if( debug )
624 System.out.println("RecvServ.run: Baue Verbindung auf");
625 srv_clnt = srv.accept();
626
627 if( debug )
628 System.out.println("RecvServ.run: Erzeuge InputStream");
629 in = new DataInputStream(srv_clnt.getInputStream());
630
631 if( debug )
632 System.out.println("RecvServ.run: Warte auf Daten");
633
634 // ByteArrayOutputStream verhaelt sich wie ein Stream
635 // und laesst sich wunderbar in ein Byte-Array zurueckwandeln
636 ByteArrayOutputStream out = new ByteArrayOutputStream();
637
638 int anz;
639 while( (anz = in.read(buf)) > 0 )
640 out.write(buf,0,anz);
641
642 data = out.toByteArray();
643
644 if( srv != null )
645 srv.close();
646 }
647 catch (IOException e)
648 {
649 HylaFaxConnectionManager.fail("RecvServ", e, HylaFaxConnectionManager.RECEIVE);
650 }
651
652 if(debug)
653 {
654 System.out.println("RecvServ.run: Thread beendet!");
655 }
656 }
657 }
658
659 /*
660 * Die Klasse SendServ ist abgeleitet von der Klasse Thread.
661 * Sie sendet Daten zum Protokoll Server
662 */
663
664 class SendServ extends Thread {
665 protected int port;
666
667 private final static int timeout = 20; // in seconds
668 private byte buf[];
669 private ServerSocket srv;
670 private Socket srv_clnt;
671 private boolean debug;
672
673 public SendServ
674 (
675 byte buf[],
676 boolean debug
677 )
678 throws java.io.IOException
679 {
680
681 this.buf = buf;
682 this.debug = debug;
683
684 // get next free port
685 srv = new ServerSocket(0, timeout);
686 port = srv.getLocalPort();
687 if( debug )
688 System.out.println("SendServ: port=" + port);
689 this.start();
690 }
691
692 // thread body
693 public void run() {
694 DataOutputStream out;
695
696 try {
697 if( debug )
698 System.out.println("SendServ.run: Baue Verbindung auf");
699 srv_clnt = srv.accept();
700
701 if( debug )
702 System.out.println("SendServ.run: Erzeuge OutputStream");
703 out = new DataOutputStream(srv_clnt.getOutputStream());
704
705 out.write(buf, 0, buf.length);
706 out.flush();
707
708 srv_clnt.close();
709
710 if( debug )
711 System.out.println("SendServ.run: Gesendete Bytes=" + out.size());
712
713 if( srv != null )
714 srv.close();
715 }
716 catch (IOException e)
717 {
718 HylaFaxConnectionManager.fail("Thread error", e, HylaFaxConnectionManager.SEND);
719 }
720
721 if(debug)
722 {
723 System.out.println("SendServ.run: Thread beendet!");
724 }
725 }
726 }
727