// $Id: udp.c,v 1.36 2007/09/21 11:05:03 lynx Exp $ // vim:syntax=lpc #if 0 /* * this is currently broken and I dont have the time to fix it */ #include #include #include #include #include "sip.h" /* * what this is about: * build a psyc presence aware SIP-Proxy that will only let friends ring * and only if you're present * * HIGHLY experimental module! use at your own risk * * functionality: * - registering 70% * tested with linphonec, kphone, shtoom, sipsak * - proxying 30% * remote user to local user 70% (rings, kphone -> linphone works) * local user to remote user 0% * - messaging ? sipsak -r 4404 -M -v -s sip:fippo@adamantine -B "lunch time" * * TODO * - better integration with core framework, e.g. using sendmsg() * instead of send_udp * */ #include #include #include #include "sip.h" inherit NET_PATH "sip/common"; send_udp_srv2(mixed *hostlist, string domain, string buf) { if (hostlist == -1) { send_udp_nonblocking(domain, 5060, buf); } else { send_udp_nonblocking(hostlist[0][DNS_SRV_NAME], hostlist[0][DNS_SRV_PORT], buf); } } send_udp_srv(domain, service, prot, buf) { dns_srv_resolve(domain, service, prot, #'send_udp_srv2, domain, buf); } reset() { } object load() { reset(); register_scheme("sip"); ::load(); return ME; } parseUDP(ip, port, msg) { string cmdline, headers, data; mapping v; string source, nick, target, cmd, prot; string reply; mixed *su, *tu; mixed authuser; int islocaltarget, authstate; P4(("sip:parseUDP(%O,%O, ...):\n", ip,port)) sscanf(msg, "%s" CRLF "%s", cmdline, msg); sscanf(msg, "%s" CRLF CRLF "%.0s", headers, data); unless(cmdline && headers) { reply = "SIP/2.0 400 Bad request!" CRLF + msg; send_udp(ip, port, reply); return; } sscanf(cmdline, "%s%t%s%t%s", cmd, target, prot); /* header parsing */ v = parseHeaders(headers); if (v == -1) { // parsing failed } // TODO: loop detection im via // do we have to check via headers vs source ip? /* response handling and generation */ if(abbrev("SIP", cmd) && (target = to_int(target))) { handleResponse(cmd, target, cmdline, v, data); #if 0 // should not happen as we are proxy if (stringp(v["via"])) { PT(("local response, generating 200\n")); reply = makeResponse(prot, 200, v); send_udp(ip, port, reply); } #endif return; } if (target) tu = parse_uniform(target); unless(tu) { reply = makeResponse(prot, 400, v); send_udp(ip, port, reply); return; } islocaltarget = is_localhost(lower_case(tu[UHost])); // TODO this may be either local or remote users if (islocaltarget && tu[UUser]) target = find_person(tu[UUser]); // TODO works only for online users #if 0 unless(target) { // ? } #endif if (v["authorization"]) { // how to integrate with net/* PSYC // infrastructure? // for now we want features and make a dirty // implementation mapping a; string method, auth; string vname, vvalue; sscanf(v["authorization"], "%s%t%s", method, auth); /* parse the string */ a = ([ "_method" : "REGISTER" ]); while(sscanf(auth, "%s=%s,%t%s", vname, vvalue, auth) >= 2) { sscanf(vvalue, "\"%s\"", vvalue); a["_" + vname] = vvalue; } sscanf(auth, "%s=\"%s\"", vname, vvalue); sscanf(vvalue, "\"%s\"", vvalue); a["_" + vname] = vvalue; a["_password"] = a["_response"]; /* end */ authuser = find_person(a["_username"]); // TODO: dont call-other 0 // TODO: this is broken as of async checkPassword // and will need a rewrite as it is getting ugly authstate = authuser -> checkPassword(a["_response"], "http-digest", a["_nonce"], a); } // TODO: if (v["from"]) { sscanf(v["from"], "%s%.0t<%s>%!s", nick, source); // TODO what about the extra part? unless(source) source = v["from"]; P2(("from source %O with nick %O\n", source, nick)) su = parse_uniform(source); source = 0; #if 0 if (is_localhost(lower_case(su[UHost]))) { object o; o = find_person(su[UUser]); // TODO: check that source is coming from // an authorized location! PT(("location %O, %s:%d\n", o -> qLocation("sip"), ip, port)) source = o; } #endif unless(source) { source = SIP + su[UUserAtHost]; #if 0 if (su[UPort] && su[UPort] != 5060) source += ":" + su[UPort]; #endif } } P2(("sip cmd %O from %O to %O\n", cmd, source, target)) switch(upper_case(cmd)) { case "OPTIONS": reply = makeResponse(prot, 400, v); send_udp(ip, port, reply); return; case "MESSAGE": if (islocaltarget) { if (data && data != "") sendmsg(target, "_message_private", data, ([ "_nick" : objectp(source) ? source -> qName() : ""]), source); // TODO: should be done via _message_private_echo // somehow... if (pointerp(v["via"])) v["via"][0] += ";received=" + __HOST_IP_NUMBER__ + ":" + query_udp_port(); reply = makeResponse(prot, 200, v); send_udp(ip, port, reply); } return; case "SUBSCRIBE": // subscribe sip:... SIP/2.0 // ein _request_friendship quasi // oder ein if (islocaltarget) { sendmsg(target, "_notice_presence_here", data, ([ "_INTERNAL_mood_jabber" : "neutral" ]), source); if (pointerp(v["via"])) v["via"][0] += ";received=" + __HOST_IP_NUMBER__ + ":" + query_udp_port(); reply = makeResponse(prot, 200, v); send_udp(ip, port, reply); } return; case "CANCEL": case "BYE": if (islocaltarget) { // TODO: secure that string branch = ";branch=z9hG4bK" + RANDHEXSTRING; string ni, tgt, ho, po; mixed o; PT(("%O should be directed from %O to %O\n", cmd, v["from"], v["to"])) sscanf(v["to"], "%s<%s>%s", ni, tgt, ni); tu = parse_uniform(tgt); PT(("going to %O\n", tu)) if(stringp(v["via"])) v["via"] = ({ SIP_UDP " " SERVER_HOST ":4404" + branch, v["via"] }); else if (pointerp(v["via"])) v["via"] = ({ SIP_UDP " " SERVER_HOST ":4404" + branch }) + v["via"]; // TODO reply = cmdline + CRLF + serialize( ({ "via", "to", "from", "call-id", "cseq", "max-forwards", "contact" }), v, data); send_udp_nonblocking("adamantine", 5060, reply); return; } else { PT(("local to remote bye/cancel\n")) } break; case "INVITE": case "ACK": // hier muessen wir zwei cases unterscheiden // a) ein remote/lokal user will einem unserer user was schicken // b) ein user von uns will einem remote user etwas // schicken // Fall a: if (islocaltarget) { string loc; if (target && (loc = target -> qLocation("sip"))) { string branch; tu = parse_uniform(loc); PT(("should proxy call from %O to %O\n", source, loc)) if (stringp(v["max-forwards"])) v["max-forwards"] = to_int(v["max-forwards"]); v["max-forwards"] -= 1; branch = ";branch=z9hG4bK" + RANDHEXSTRING; if(stringp(v["via"])) v["via"] = ({ SIP_UDP " " SERVER_HOST ":4404" + branch, v["via"] }); else if (pointerp(v["via"])) v["via"] = ({ SIP_UDP " " SERVER_HOST ":4404" + branch}) + v["via"]; // PSYC integration #if 0 if (cmd == "INVITE") sendmsg(target, "_notice_location_sip_ring", "[_source] is ringing your SIP phone at [_location]", ([ "_source" : source, "_location" : loc ]), "sip:" + source); #endif reply = cmd + " " + loc + " " + prot + CRLF + serialize( ({ "via", "to", "from", "call-id", "cseq", "max-forwards", "contact" }), v, data); send_udp_nonblocking(tu[UHost], to_int(tu[UPort]) || 5060, reply); } } else { // der Part ist wohl doch zu kompliziert // magic branch cookie as of RFC 3261, 8.1.1.7 string branch = ";branch=" SIP_MAGIC_COOKIE; // Section 16.6 // unless (su) ?? if (su[UHost] != SERVER_HOST) return -1; // remote to remote?? source = find_person(su[UUser]); PT(("local user %O is calling to %O\n", source, target)) PT(("contact %O\n", v["contact"])) PT(("authorized? %O == %O\n", source -> qLocation("sip"), "sip:" + ip + ":" + port)) // step 4 if (stringp(v["max-forwards"])) v["max-forwards"] = to_int(v["max-forwards"]); else v["max-forwards"] = 10; v["max-forwards"] -= 1; // step 8 branch += RANDHEXSTRING; if(stringp(v["via"])) v["via"] = ({ SIP_UDP " " SERVER_HOST ":4404" + branch, v["via"] }); else if (pointerp(v["via"])) v["via"] = ({ SIP_UDP " " SERVER_HOST ":4404" + branch }) + v["via"]; reply = cmd + " " + target + " " SIP_UDP CRLF + serialize( ({ "via", "to", "from", "call-id", "cseq", "max-forwards", "contact" }), v, data); // step 10 PT(("data %O\nreply %O\n", data, reply)) send_udp_srv(tu[UHost], "sip", "udp", reply); return; } break; case "REGISTER": if (authstate) { int expires; string loc; mixed *cu; expires = to_int(v["expires"]); P2(("contact(s) %O expires in %d seconds\n", v["contact"], expires)) // TODO loc = v["contact"]; if (!v["contact"]) { // query for all contacts } sscanf(v["contact"], "%!.0s<%s>%!.0s", loc); cu = parse_uniform(loc); if (cu) { loc = SIP + cu[UUserAtHost]; } if (stringp(v["via"])) v["via"] += ";received=" + __HOST_IP_NUMBER__ + ":" + query_udp_port(); else if (pointerp(v["via"])) v["via"][0] += ";received=" + __HOST_IP_NUMBER__ + ":" + query_udp_port(); reply = makeResponse(prot, 200, v, ""); if (expires!= 0) { reply += serialize(({ "contact" }), v); } send_udp(ip, port, reply); /* IF authstate == 1 * expire != 0: * set users sip-location to v["contact"] * for expire seconds * else delete users sip-location */ if (expires != 0) { // TODO: this gets annoying, // should only be shown if changed // TODO: how do we deal with expires? if (authuser -> sLocation("sip", loc)) sendmsg(authuser, "_notice_location_sip", "Your SIP Phone ([_version_agent]) has registered at " "[_location_sip] (valid for [_duration] seconds).", ([ "_location_sip" : loc, "_version_agent" : v["user-agent"], "_duration" : v["expires"] ]), authuser); } else { if (authuser -> sLocation("sip", 0)) sendmsg(authuser, "_notice_location_sip_signoff", "Your SIP Phone has signed off from [_location_sip].", ([ "_location_sip" : v["contact"] ]), authuser); } } else if (v["authorization"]) { // authorization failed reply = makeResponse(prot, 403, v, ""); // reply += "Content-Length: 0" CRLF CRLF; send_udp(ip, port, reply); } else { //v["to"] += ";tag=1234567890abcde"; // ??? TODO /* need authorization */ v["www-authenticate"] = "Digest realm=\"" + SERVER_UNIFORM + "\", nonce=\"" + time() + "\""; reply = makeResponse(prot, 401, v, ""); // TODO: nonce generation has to be secure send_udp(ip, port, reply); } return; default: reply = makeResponse(prot, 400, v, ""); send_udp(ip, port, reply); return; } // RFC 3261, figure 1 // // [...] This response contains the same To, From, Call-ID, CSeq and // branch parameter in the via as the invite reply = makeResponse(prot, 100, v); reply += "Content-Length: 0" CRLF CRLF; send_udp(ip, port, reply); return; } handleResponse(prot, statuscode, cmdline, v, data) { /* status message, send to topmost via-header */ string target, host, tag; // tag == branch??? string reply; int port; PT(("via %O\n", v["via"])) if (stringp(v["via"])) { // TODO response is addressed to us PT(("local response code %O\n", statuscode)) return; } sscanf(v["via"][1], "%s%t%s", prot, target); sscanf(target, "%s;%s", target, tag); sscanf(target, "%s:%d", host, port); v["via"] = v["via"][1..]; unless(host) host = target; unless(port) port = 5060; PT(("sending status %O to %s:%d\n", statuscode, host, port)) reply = cmdline + CRLF + serialize( ({ "via", "to", "from", "call-id", "cseq", "contact" }), v, data); send_udp_nonblocking(host, port, reply); return; } msg(source, mc, data, mapping vars, showingLog, target) { mapping v; string packet; PT(("should send %O from %O to %O\nvars %O\n", mc, source, target, vars)) // note: stpeters xmpp2sip work may be - though outdated - useful #if 0 v = ([ "content-type" : vars["_type_data"] || "text/plain" ]); packet = ""; switch(mc) { case "_message_private": v["via"] = SIP_UDP " " SERVER_HOST ";branch=z9hG4bK" + RANDHEXSTRING; v["cseq"] = random(__INT_MAX__) + " MESSAGE"; v["to"] = target; v["from"] = source; v["call-id"] = RANDHEXSTRING + "@" SERVER_HOST; packet = "MESSAGE " + target + " SIP/2.0" CRLF + serialize(({ "to", "from", "via", "call-id", "cseq" }), v, data); PT(("packet %O\n", packet)) break; } #endif return; } #endif