package jpbx.plugins.core; import java.util.*; import javaforce.*; import jpbx.core.*; /** Low-level plugin for handling INVITEs to extensions. */ public class Extensions implements Plugin, DialChain { public final static int pid = 5; //priority private PBXAPI api; //interface Plugin public void init(PBXAPI api) { this.api = api; api.hookDialChain(this); JFLog.log("Extensions plugin init"); } public void uninit(PBXAPI api) { api.unhookDialChain(this); JFLog.log("Extensions plugin uninit"); } public void install(PBXAPI api) { //nothing to do } public void uninstall(PBXAPI api) { //nothing to do } //interface DialChain public int getPriority() {return pid;} public int onInvite(CallDetailsPBX cd, SQL sql, boolean src) { JFLog.log("check invite" + cd.dialed); if (cd.invited) { //reINVITE api.connect(cd); if (src) { cd.pbxdst.branch = cd.src.branch; cd.pbxsrc.branch = cd.src.branch; } else { cd.pbxdst.branch = cd.dst.branch; cd.pbxsrc.branch = cd.dst.branch; } cd.sip.buildsdp(cd, src ? cd.pbxdst : cd.pbxsrc); api.issue(cd, null, true, !src); return pid; } if (!src) return -1; if (!cd.authorized) { if (!apply_inbound_routes(cd, sql)) return -1; } //dial inbound to an extension String ext = sql.select1value("SELECT ext FROM exts WHERE ext=" + sql.quote(cd.dialed)); if (ext == null) { JFLog.log("err0"); return -1; } //an extension is not being dialed if (cd.user.equals(cd.dialed)) { //did someone call themself? JFLog.log("err1"); return -1; //voicemail will handle this call } if (!api.isRegistered(cd.dialed)) { JFLog.log("err2"); return -1; //phone is not online } Extension x = api.getExtension(cd.dialed); if (x == null) { JFLog.log("err3"); return -1; //phone is not online } cd.pbxdst.contact = cd.src.contact; cd.pbxdst.branch = cd.src.branch; cd.pbxdst.to = cd.src.to.clone(); cd.pbxdst.from = cd.src.from.clone(); api.reply(cd, 100, "TRYING", null, false, true); cd.pbxdst.cseq = 1; cd.pbxdst.host = x.remoteip; cd.pbxdst.port = x.remoteport; cd.uri = "sip:" + cd.dialed + "@" + cd.pbxdst.host + ":" + cd.pbxdst.port; api.connect(cd); cd.sip.buildsdp(cd, cd.pbxdst); api.issue(cd, null, true, false); cd.invited = true; setTimer(cd, 15000); api.log(cd, "DEBUG:INVITE:" + cd.dialed + ":dst=" + cd.pbxdst.host + ":" + cd.pbxdst.port); return pid; } public void onRinging(CallDetailsPBX cd, SQL sql, boolean src) { if (src) return; api.reply(cd, 180, "RINGING", null, false, true); } public void onSuccess(CallDetailsPBX cd, SQL sql, boolean src) { if (!cd.cmd.equals("INVITE")) return; synchronized(cd.lock) { if (cd.cancelled) return; cd.connected = true; } clearTimer(cd); //send ack cd.cmd = "ACK"; api.issue(cd, null, false, src); //send ACK if (src) { cd.pbxdst.to = cd.src.to.clone(); cd.pbxdst.from = cd.src.from.clone(); } else { cd.pbxsrc.to = cd.dst.to.clone(); cd.pbxsrc.from = cd.dst.from.clone(); } api.connect(cd); cd.sip.buildsdp(cd, src ? cd.pbxdst : cd.pbxsrc); cd.cmd = "INVITE"; api.reply(cd, 200, "OK", null, true, !src); //send 200 (NOTE : ACK is ignored) } public void onCancel(CallDetailsPBX cd, SQL sql, boolean src) { if (!src) return; synchronized(cd.lock) { if (cd.connected) return; //TODO : should return error already connected cd.cancelled = true; } api.reply(cd, 200, "OK", null, false, true); api.issue(cd, null, false, false); api.disconnect(cd); } public void onError(CallDetailsPBX cd, SQL sql, int code, boolean src) { api.reply(cd, code, "RELAY", null, false, !src); cd.cmd = "ACK"; api.issue(cd, null, false, src); } public void onTrying(CallDetailsPBX cd, SQL sql, boolean src) { } public void onBye(CallDetailsPBX cd, SQL sql, boolean src) { if (src) { //NOTE:to/from have been swapped cd.pbxdst.to = cd.src.to.clone(); cd.pbxdst.from = cd.src.from.clone(); cd.pbxdst.cseq++; api.issue(cd, null, false, false); cd.pbxsrc.to = cd.src.to.clone(); cd.pbxsrc.from = cd.src.from.clone(); cd.pbxsrc.cseq++; api.reply(cd, 200, "OK", null, false, true); } else { //NOTE:to/from have been swapped cd.pbxsrc.to = cd.dst.to.clone(); cd.pbxsrc.from = cd.dst.from.clone(); cd.pbxsrc.cseq++; api.issue(cd, null, false, true); cd.pbxdst.to = cd.dst.to.clone(); cd.pbxdst.from = cd.dst.from.clone(); cd.pbxdst.cseq++; api.reply(cd, 200, "OK", null, false, false); } api.disconnect(cd); } public void onFeature(CallDetailsPBX cd, SQL sql, String cmd, String cmddata, boolean src) { } private void setTimer(CallDetailsPBX cd, int timeout) { api.log(cd, "setting timer for call"); cd.timer = new Timer(); cd.timer.schedule(new TimerTask() { private CallDetailsPBX cd; public void run() { synchronized (cd.lock) { cd.timer = null; if (cd.connected) return; api.log(cd, "Trying to do voicemail for ext=" + cd.dialed); //send CANCEL to dst cd.cmd = "CANCEL"; api.issue(cd, null, false, false); SQL sql = new SQL(); if (!sql.connect(Service.jdbc)) return; //ohoh //check if ext has voicemail? String value = sql.select1value("SELECT value FROM extopts WHERE ext=" + sql.quote(cd.dialed) + " AND id='vm'"); if ((value != null) && (value.equals("yes"))) { //transfer call to voicemail after timeout cd.invited = false; cd.pid = VoiceMail.pid; api.onInvite(cd, sql, true, cd.pid); } else { //reply 486 (user is busy) cd.cmd = "INVITE"; api.reply(cd, 486, "NO ONE HERE", null, false, true); } sql.close(); } } public TimerTask init(CallDetailsPBX cd) { this.cd = cd; return this; } }.init(cd), timeout); //give ext timeout millisec to respond } private void clearTimer(CallDetailsPBX cd) { synchronized (cd.lock) { if (cd.timer == null) return; cd.timer.cancel(); cd.timer = null; } } private boolean apply_inbound_routes(CallDetailsPBX cd, SQL sql) { String routes[][] = sql.select("SELECT cid,did,dest FROM inroutes"); //cd.user = cid //cd.dialed = did for(int route=0;route<routes.length;route++) { if (routes[route][0].length() == 0 && routes[route][1].length() == 0) continue; //bad route if (routes[route][2].length() == 0) continue; //bad route if ((routes[route][0].length() > 0) && !routes[route][0].equals(cd.user)) continue; //no cid match if ((routes[route][1].length() > 0) && !routes[route][1].equals(cd.dialed)) continue; //no did match cd.dialed = routes[route][2]; //set new destination return true; } String anon = sql.select1value("SELECT value FROM config WHERE id='anon'"); String route = sql.select1value("SELECT value FROM config WHERE id='route'"); cd.anon = anon.equals("true"); cd.route = route.equals("true"); return cd.anon; //FALSE = no anonymous inbound calls } }