/*The following code is instructive if you're learning how to do networking /*and threads in Java. It defines the ServerThread class. /****************************************************************************/ /* */ /* Copyright (c) 1995-1997 The Fractal Images Company. All Rights Reserved. /* File name: ServerThread.java */ /* */ /* Author: Robert Uomini */ /****************************************************************************/ import java.util.*; import java.io.*; import java.net.*; public final class ServerThread extends Thread { Socket inbound; DataInputStream istream = null; BufferedOutputStream ostream = null; String str = null; String reply = null; String group_name = null; String host_name; String subscribed_groups = ""; String unsubscribed_groups = ""; String article_num = null; String welcome_msg; String command; String offline_dir = "offline_articles"; String cache_dir = "post_queue"; InetAddress local_addr = null; InetAddress inet_addr; byte byte_array[]; int index; boolean done; public ServerThread(Socket inbound) { this.inbound = inbound; } /****************************************************************************/ /* A ServerThread class is instantiated by OfflineServer, whenever */ /* WebReader goes offline or is started in offline mode. First, certain */ /* stream classes are created for handling requests from, and responses */ /* to, the client. The local host IP address is also fetched. If the IP */ /* address of the host issuing the client request, the request is junked */ /* and the connection is terminated. */ /****************************************************************************/ public void run() { String group_names; try { istream = new DataInputStream(inbound.getInputStream()); ostream = new BufferedOutputStream(inbound.getOutputStream()); } catch (IOException e) { System.out.println("Error opening stream: " + e); System.exit(0); } try { local_addr = InetAddress.getLocalHost(); } catch (UnknownHostException e){} inet_addr = inbound.getInetAddress(); if (local_addr.toString().indexOf(inet_addr.toString()) != -1 || inet_addr.toString().equals("127.0.0.1")) { welcome_msg = "200 " + local_addr; done = false; } else { welcome_msg = "502 NNTP server can't talk to you. Goodbye."; done = true; } welcome_msg += "\n"; byte_array = new byte[welcome_msg.length()]; /****************************************************************************/ /* Send the reply to the client's open request. Stay in the loop, reading */ /* and processing client requests until either a null request or a Quit */ /* command is received. */ /****************************************************************************/ welcome_msg.getBytes(0, welcome_msg.length(), byte_array, 0); try { ostream.write(byte_array); ostream.flush(); } catch (IOException e){} while(!done) { try { str = istream.readLine(); } catch (IOException e){} if (str == null || str.trim().toUpperCase().equals("QUIT")) { done = true; } else { /****************************************************************************/ /* Process each of the valid NNTP server command strings in turn. */ /****************************************************************************/ command = str.toUpperCase(); if (command.startsWith("ARTICLE ")) { article_num = str.substring("ARTICLE ".length()); reply = get_article(group_name, article_num, 220); } if (command.startsWith("AUTHINFO ")) { reply = "281 Authentication accepted"; } if (command.startsWith("GROUP ")) { group_name = str.substring("GROUP ".length()); reply = get_group_header(group_name); } if (command.startsWith("HEAD ")) { article_num = str.substring("HEAD ".length()); reply = get_article(group_name, article_num, 221); if ((index = reply.indexOf("\n\n")) != -1) reply = reply.substring(0, index); reply += "\n."; } if (command.startsWith("HELP")) { reply = "100 Legal commands\n" + " article number\n" + " authinfo user name\n" + " group newsgroup\n" + " head number\n" + " help\n" + " list\n" + " mode reader\n" + " newgroups yymmdd hhmmss\n" + " post\n" + " xover number\n."; } if (command.equals("LIST")) { subscribed_groups = list_groups("Newsrc.subscribed", host_name); unsubscribed_groups = list_groups("Newsrc.unsubscribed", host_name); reply = "215 list of newsgroups follows" + "\n" + subscribed_groups + unsubscribed_groups + "."; } if (command.startsWith("LIST NEWSGROUPS ")) { reply = get_desc(str.substring("LIST NEWSGROUPS ".length()), host_name); } if (command.startsWith("MODE READER")) { reply = "200 Hello, you can post"; } if (command.startsWith("NEWGROUPS ")) { reply = "231 New newsgroups follow.\n."; } if (command.startsWith("POST")) { reply = "340 Ok"; reply += "\n"; byte_array = new byte[reply.length()]; reply.getBytes(0, reply.length(), byte_array, 0); try { ostream.write(byte_array); ostream.flush(); } catch (IOException e){} reply = ""; do { try { str = istream.readLine(); } catch (IOException e) { reply = "441 posting failed"; continue; } if (str != null) reply += str + "\n"; } while(!str.equals(".")); index = reply.indexOf(WRLiterals.NEWSGPS_HDR) + WRLiterals.NEWSGPS_HDR.length(); group_names = reply.substring(index); group_names = group_names.substring(0, group_names.indexOf("\n")); if (group_names.length() == 0) { reply = "441 Required \"Newsgroups\" header is missing"; } else { reply = reply.substring(0, reply.lastIndexOf(".")).trim(); reply = save_article(cache_dir, group_names, reply); } } if (command.startsWith("SET HOSTNAME ")) { host_name = str.substring("SET HOSTNAME ".length()); if (host_name != null) { cache_dir += "." + host_name; offline_dir += "." + host_name; } reply = "200 File names set"; } if (command.startsWith("XOVER ")) { article_num = str.substring("XOVER ".length()); reply = get_header(group_name, article_num); } if (command.length() < 3) { reply = command; } if (!command.startsWith("ARTICLE ") && !command.startsWith("AUTHINFO ") && !command.startsWith("GROUP ") && !command.startsWith("HEAD ") && !command.startsWith("HELP") && !command.equals("LIST") && !command.startsWith("LIST NEWSGROUPS ") && !command.startsWith("MODE READER") && !command.startsWith("NEWGROUPS ") && !command.startsWith("POST") && !command.startsWith("SET HOSTNAME") && !command.startsWith("XOVER ")) { reply = "500 command not recognized"; } reply += "\n"; byte_array = new byte[reply.length()]; reply.getBytes(0, reply.length(), byte_array, 0); try { ostream.write(byte_array); ostream.flush(); } catch (IOException e){} } str = null; } try { inbound.close(); } catch (IOException e){} } /****************************************************************************/ /* /****************************************************************************/ /* The list_groups() method processes the LIST command. */ /****************************************************************************/ public String list_groups(String file_name, String host_name) { RandomAccessFile groups_file; String tmp = ""; StringBuffer groups; int index; if (host_name.length() > 0) file_name += "." + host_name; try { groups_file = new RandomAccessFile(file_name, "r"); } catch (IOException e) { return ""; } try { tmp = groups_file.readLine(); } catch (IOException e) { return ""; } groups = new StringBuffer(); for(; tmp != null;) { try { tmp = groups_file.readLine(); } catch (IOException e) { return ""; } if (tmp != null) { if ((index = tmp.indexOf(WRLiterals.COLON)) != -1 || (index = tmp.indexOf(WRLiterals.EXCLAM)) != -1) { groups.append(tmp.substring(0, index) + "\n"); } } } return groups.toString(); } /****************************************************************************/ /* The get_group_header() method processes the GROUP command. */ /****************************************************************************/ public String get_group_header(String group) { File tmp_file; RandomAccessFile save_file = null; String tmp_name; String header = null; tmp_file = new File(offline_dir); tmp_name = tmp_file.getAbsolutePath() + tmp_file.separator + group; try { save_file = new RandomAccessFile(tmp_name, "r"); } catch (IOException e) { return "411"; } try { header = save_file.readLine(); } catch (IOException e) { return "411"; } return header; } /****************************************************************************/ /* The get_header() method processes the XOVER command. */ /****************************************************************************/ public String get_header(String group, String article_num) { File tmp_file; RandomAccessFile save_file = null; String tmp_name; String first_article; String last_article; String header = null; String reply; String num_articles; String article = null; int index; int first_art; int last_art; int art_num; int num_art; if (group == null) return "412"; tmp_file = new File(offline_dir); tmp_name = tmp_file.getAbsolutePath() + tmp_file.separator + group; try { save_file = new RandomAccessFile(tmp_name, "r"); } catch (IOException e) { return "412"; } try { header = save_file.readLine(); } catch (IOException e) { return "420"; } first_article = header.substring(header.indexOf(WRLiterals.BLANK) + 1); num_articles = first_article.substring(0, first_article.indexOf(WRLiterals.BLANK)); first_article = first_article.substring(first_article.indexOf(WRLiterals.BLANK) + 1); last_article = first_article.substring(first_article.indexOf(WRLiterals.BLANK) + 1, first_article.lastIndexOf(WRLiterals.BLANK)); first_article = first_article.substring(0, first_article.indexOf(WRLiterals.BLANK)); num_art = Integer.parseInt(num_articles); first_art = Integer.parseInt(first_article); last_art = Integer.parseInt(last_article); art_num = Integer.parseInt(article_num); if (art_num < first_art || art_num > last_art) return "420"; header = get_selected_header(save_file, article_num, num_art); if (header == null) return "420"; reply = "224 data follows" + "\n" + header; return reply; } /****************************************************************************/ /* The get_selected_header() method is called by the get_header() method, */ /* and fetches a specific article header. */ /****************************************************************************/ public String get_selected_header(RandomAccessFile save_file, String article_num, int num_art) { String curr_artnum; String start_ptr; String end_ptr; String subject; String author; String date; String msg_id; String refs; String lines; String header; String tmp; byte data[]; long strt = 0; long end = 0; int index; int index2; int data_len; boolean article_found; data = new byte[16 * num_art]; try { save_file.read(data); } catch (IOException e) { return null; } tmp = new String(data, 0); for(index = 0, article_found = false; !article_found && index < num_art; index++) { curr_artnum = tmp.substring(0, 8).trim(); if (curr_artnum.equals(article_num)) { start_ptr = tmp.substring(8, 16).trim(); strt = Long.parseLong(start_ptr); if (tmp.length() > 16) { end_ptr = tmp.substring(24, 32).trim(); end = Long.parseLong(end_ptr); } else { try { end = save_file.length(); } catch (IOException e) { return null; } } article_found = true; } tmp = tmp.substring(16); } if (!article_found) return null; try { save_file.seek(strt); } catch (IOException e) { return null; } header = ""; data_len = (int)(end - strt); subject = ""; author = ""; date = ""; msg_id = ""; refs = ""; lines = ""; try { tmp = save_file.readLine(); } catch (IOException e) { return null; } for(; tmp.length() > 3;) { if ((index = tmp.indexOf(WRLiterals.SUBJECT_HDR)) != -1) { subject = tmp.substring(index + WRLiterals.SUBJECT_HDR.length()); } if ((index = tmp.indexOf(WRLiterals.FROM_HDR)) != -1) { author = tmp.substring(index + WRLiterals.FROM_HDR.length()); } if ((index = tmp.indexOf(WRLiterals.DATE_HDR)) != -1) { date = tmp.substring(index + WRLiterals.DATE_HDR.length()); } if ((index = tmp.indexOf(WRLiterals.MSGID_HDR)) != -1) { msg_id = tmp.substring(index + WRLiterals.MSGID_HDR.length()); } if ((index = tmp.indexOf(WRLiterals.REFERENCES_HDR)) != -1) { refs = tmp.substring(index + WRLiterals.REFERENCES_HDR.length()); } if ((index = tmp.indexOf(WRLiterals.LINES)) != -1) { lines = tmp.substring(index + WRLiterals.LINES.length()); } try { tmp = save_file.readLine(); } catch (IOException e) { return null; } } header = article_num + "\t" + subject + "\t" + author + "\t" + date + "\t" + msg_id + "\t" + refs + "\t" + data_len + "\t" + lines + "\t" + "\n."; return header; } /****************************************************************************/ /* The get_article() method processes the ARTICLE command and is also used */ /* by in processing of the HEAD command. */ /****************************************************************************/ public String get_article(String group, String article_num, int rc) { File tmp_file; RandomAccessFile save_file = null; String tmp_name; String first_article; String last_article; String header = null; String reply; String msg_id; String num_articles; String article = null; int index; int first_art; int last_art; int art_num; int num_art; if (group == null) return "412"; tmp_file = new File(offline_dir); tmp_name = tmp_file.getAbsolutePath() + tmp_file.separator + group; try { save_file = new RandomAccessFile(tmp_name, "r"); } catch (IOException e) { return "412"; } try { header = save_file.readLine(); } catch (IOException e) { return "430"; } first_article = header.substring(header.indexOf(WRLiterals.BLANK) + 1); num_articles = first_article.substring(0, first_article.indexOf(WRLiterals.BLANK)); first_article = first_article.substring(first_article.indexOf(WRLiterals.BLANK) + 1); last_article = first_article.substring(first_article.indexOf(WRLiterals.BLANK) + 1, first_article.lastIndexOf(WRLiterals.BLANK)); first_article = first_article.substring(0, first_article.indexOf(WRLiterals.BLANK)); num_art = Integer.parseInt(num_articles); first_art = Integer.parseInt(first_article); last_art = Integer.parseInt(last_article); art_num = Integer.parseInt(article_num); if (art_num < first_art || art_num > last_art) return "423"; article = get_selected_article(save_file, article_num, num_art); if (article == null) return "430"; article = article.substring(article.indexOf(WRLiterals.BLANK)).trim(); msg_id = "<0>"; header = article.substring(0, article.indexOf("\n\n") + 2); if ((index = header.indexOf(WRLiterals.MSGID_HDR)) != -1) { msg_id = header.substring(index + WRLiterals.MSGID_HDR.length()); msg_id = msg_id.substring(0, msg_id.indexOf("\n")); } reply = rc + WRLiterals.BLANK + article_num + WRLiterals.BLANK + msg_id + "\n" + article + "\n."; return reply; } /****************************************************************************/ /* The get_selected_article() method is called by the get_article() method,*/ /* and fetches a specific article. */ /****************************************************************************/ public String get_selected_article(RandomAccessFile save_file, String article_num, int num_art) { String curr_artnum; String start_ptr; String end_ptr; String tmp; byte data[]; long strt = 0; long end = 0; int index; boolean article_found; data = new byte[16 * num_art]; try { save_file.read(data); } catch (IOException e) { return null; } tmp = new String(data, 0); for(index = 0, article_found = false; !article_found && index < num_art; index++) { curr_artnum = tmp.substring(0, 8).trim(); if (curr_artnum.equals(article_num)) { start_ptr = tmp.substring(8, 16).trim(); strt = Long.parseLong(start_ptr); if (tmp.length() > 16) { end_ptr = tmp.substring(24, 32).trim(); end = Long.parseLong(end_ptr); } else { try { end = save_file.length(); } catch (IOException e) { return null; } } article_found = true; } tmp = tmp.substring(16); } if (!article_found) return null; try { save_file.seek(strt); } catch (IOException e) { return null; } data = new byte[(int)(end - strt)]; try { save_file.read(data); } catch (IOException e) { return null; } return new String(data, 0); } /****************************************************************************/ /* The get_desc() method processes the LIST NEWSGROUPS command. */ /****************************************************************************/ public String get_desc(String group_name, String host_name) { String subgp_file_name = "Newsrc.subscribed"; String unsubgp_file_name = "Newsrc.unsubscribed"; String subdesc_file_name = "desc.subscribed"; String unsubdesc_file_name = "desc.unsubscribed"; String file_names[]; String desc; int index; if (host_name.length() > 0) { subgp_file_name += "." + host_name; unsubgp_file_name += "." + host_name; subdesc_file_name += "." + host_name; unsubdesc_file_name += "." + host_name; } file_names = new String[4]; file_names[0] = subgp_file_name; file_names[1] = subdesc_file_name; file_names[2] = unsubgp_file_name; file_names[3] = unsubdesc_file_name; for(index = 0, desc = null; desc == null && index < file_names.length; index += 2) { desc = fetch_desc(group_name, file_names[index], file_names[index + 1]); } if (desc == null) return "503 group not found"; return "215 information follows" + "\n" + desc + "\n."; } /****************************************************************************/ /* The fetch_desc() method is called by the get_desc() method, and is */ /* responsible for actually fetching the group description. */ /****************************************************************************/ public String fetch_desc(String group_name, String file_name, String desc_name) { RandomAccessFile groups_file; String tmp_name; String desc; StringBuffer groups; int index = 0; int name_index; int index2; boolean found; try { groups_file = new RandomAccessFile(file_name, "r"); } catch (IOException e) { return null; } try { desc = groups_file.readLine(); } catch (IOException e) { return null; } for(name_index = 0, found = false; !found && desc != null; name_index++) { try { desc = groups_file.readLine(); } catch (IOException e) { return null; } if (desc != null) { if ((index = desc.indexOf(WRLiterals.COLON)) == -1) index = desc.indexOf(WRLiterals.EXCLAM); if (desc.substring(0, index).equals(group_name)) found = true; } } if (desc == null) return null; try { groups_file = new RandomAccessFile(desc_name, "r"); } catch (IOException e) { return null; } try { desc = groups_file.readLine(); } catch (IOException e) { return null; } for(index2 = 0; index2 < name_index; index2++) { try { desc = groups_file.readLine(); } catch (IOException e) { return null; } } return desc; } /****************************************************************************/ /* The save_article() method is used in processing the POST command, and */ /* serves to save the article to disk. */ /****************************************************************************/ public static String save_article(String file_dir, String group_names, String article_text) { File tmp_file; RandomAccessFile save_file = null; String tmp_name; StringTokenizer st; st = new StringTokenizer(group_names, ","); while (st.hasMoreTokens()) { tmp_file = new File(file_dir); tmp_name = tmp_file.getAbsolutePath() + tmp_file.separator + st.nextToken().trim(); try { save_file = new RandomAccessFile(tmp_name, "rw"); } catch (IOException e) { return "441 posting failed"; } try { save_file.seek(save_file.length()); save_file.writeBytes(article_text.length() + 1 + "\n"); save_file.writeBytes(article_text + "\n"); } catch (IOException e) { return "441 posting failed"; } } return "240 article posted ok"; } }