ref: d54c4ffc32c15d75e89c52e70dbbc8212d19ec76
parent: 2e99eaaea1aa6776eda10f5ec8bd90adcfe8424a
author: rodri <rgl@antares-labs.eu>
date: Mon Jul 31 06:56:24 EDT 2023
got referential REGISTER procedure working.
--- /dev/null
+++ b/catphone.man
@@ -1,0 +1,33 @@
+.TH MUSW 1
+.SH NAME
+catphone \- VoIP softphone worth a cat
+.SH SYNOPSIS
+.B catphone
+[
+.B -d
+]
+.I dialstring
+.I user
+.I password
+.SH DESCRIPTION
+.I Catphone
+is a VoIP softphone operated by a cat.
+.SH EXAMPLE
+.IP
+.EX
+% catphone udp!pbx.only9fans.com!sip bob secretodebob
+.EE
+.SH SOURCE
+.B /sys/src/cmd/catphone
+.SH SEE ALSO
+.IR catclock (1)
+.br
+/lib/rfc/rfc3261
+.br
+/lib/rfc/rfc8760
+.br
+Gonzalo Camarillo, “SIP Demystified”, McGraw-Hill, 2001.
+.SH BUGS
+.SH HISTORY
+.I Catphone
+first appeared in 9front (August, 2023).
--- a/dat.h
+++ b/dat.h
@@ -13,6 +13,10 @@
REFER
} SipMethod;
+typedef enum {
+ AMD5,
+} SipAuthAlgo;
+
/* rfc3261 § 21 - Response Codes */
typedef enum {
/* 1xx Provisional */
@@ -99,8 +103,25 @@
struct Sipmsg
{
Hdrtab;
- char *version;
+
+ struct {
+ char *algo;
+ char *realm;
+ char *nonce;
+ char *response;
+ } auth;
+
+ /* request */
SipMethod method;
+ char *uri;
+
+ /* response */
+ int code;
+ char *reason;
+
+ char *version;
+ ulong len;
+ char body[];
};
/* SIP UAC (see rfc3261 § 8.1, 12.1.2) */
@@ -113,5 +134,7 @@
int (*reg)(Sip*, char*, char*);
};
+
+#pragma varargck type "S" Sipmsg*
extern int debug;
--- a/fns.h
+++ b/fns.h
@@ -2,9 +2,16 @@
void *erealloc(void*, ulong);
Image *eallocimage(Display*, Rectangle, ulong, int, ulong);
+int Sfmt(Fmt*);
+void SIPfmtinstall(void);
+
void addheader(Hdrtab*, char*, char*);
Hdr *getheader(Hdrtab*, char*);
void delheader(Hdrtab*, char*);
+void delheaders(Hdrtab*);
+
+Sipmsg *newsipmsg(void);
+void delsipmsg(Sipmsg*);
Sip *mksip(int);
void rmsip(Sip*);
--- a/main.c
+++ b/main.c
@@ -22,6 +22,7 @@
char *addr;
int fd;
+ SIPfmtinstall();
ARGBEGIN{
default: usage();
case 'd':
--- a/sip.c
+++ b/sip.c
@@ -1,11 +1,14 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
+#include <mp.h>
+#include <libsec.h>
#include <draw.h>
#include "dat.h"
#include "fns.h"
static char sipversion[] = "SIP/2.0";
+static char useragent[] = "catphone (plan9front)";
static char *methodstrtab[] = {
[REGISTER] "REGISTER",
[INVITE] "INVITE",
@@ -21,18 +24,6 @@
[REFER] "REFER",
};
-static char registerhdr0[] = "REGISTER sip:%s %s\r\n"
- "Via: %s/UDP %s:%s;branch=z9hG4bK703d971c0c737b8e;rport\r\n"
- "Contact: <sip:%s-0x82a66a010@%s:%s>;expires=3849\r\n"
- "Max-Forwards: 70\r\n"
- "To: <sip:%s@%s>\r\n"
- "From: <sip:%s@%s>;tag=4a5a693256d38cbc\r\n"
- "Call-ID: 2cee372fc4be4e45\r\n"
- "CSeq: 16021 REGISTER\r\n"
- "User-Agent: catphone (plan9front)\r\n"
- "Allow: INVITE,ACK,BYE,CANCEL,OPTIONS,NOTIFY,SUBSCRIBE,INFO,MESSAGE,UPDATE,REFER\r\n"
- "Content-Length: 0\r\n"
- "\r\n";
static char registerhdr[] = "REGISTER sip:10.0.0.104 SIP/2.0\r\n"
"Via: SIP/2.0/UDP 10.0.1.9:54022;branch=z9hG4bKdf800c31b9a88ffb;rport\r\n"
"Contact: <sip:sam-0x82a66a010@10.0.1.9:54022>;expires=3849\r\n"
@@ -47,6 +38,13 @@
"Content-Length: 0\r\n"
"\r\n";
+static void
+strtolower(char *s)
+{
+ while(*s)
+ *s++ = tolower(*s);
+}
+
static char *
getmethodstr(SipMethod m)
{
@@ -53,6 +51,52 @@
return methodstrtab[m];
}
+static char *
+md5authfn(char *user, char *pass, char *uri, Sipmsg* m)
+{
+ uchar h1d[MD5dlen], h2d[MD5dlen], rd[MD5dlen];
+ char h1ds[2*MD5dlen+1], h2ds[2*MD5dlen+1];
+ static char rds[2*MD5dlen+1];
+ char buf[4096];
+
+ snprint(buf, sizeof buf, "%s:%s:%s", user, m->auth.realm, pass);
+ if(debug)
+ fprint(2, "h1: %s\n", buf);
+ md5((uchar*)buf, strlen(buf), h1d, nil);
+ snprint(buf, sizeof buf, "%s:%s", getmethodstr(m->method), uri);
+ if(debug)
+ fprint(2, "h2: %s\n", buf);
+ md5((uchar*)buf, strlen(buf), h2d, nil);
+
+ enc16(h1ds, sizeof h1ds, h1d, sizeof h1d);
+ enc16(h2ds, sizeof h2ds, h2d, sizeof h2d);
+ h1ds[nelem(h1ds)-1] = 0;
+ h2ds[nelem(h2ds)-1] = 0;
+ strtolower(h1ds);
+ strtolower(h2ds);
+ if(debug)
+ fprint(2, "h1ds: %s\nh2ds: %s\n", h1ds, h2ds);
+
+ snprint(buf, sizeof buf, "%s:%s:%s", h1ds, m->auth.nonce, h2ds);
+ if(debug)
+ fprint(2, "r: %s\n", buf);
+ md5((uchar*)buf, strlen(buf), rd, nil);
+ enc16(rds, sizeof rds, rd, sizeof rd);
+ rds[nelem(rds)-1] = 0;
+ strtolower(rds);
+ if(debug)
+ fprint(2, "rds: %s\n", rds);
+
+ return rds;
+}
+
+static struct {
+ char *name;
+ char *(*fn)(char*, char*, char*, Sipmsg*);
+} algos[] = {
+ [AMD5] { .name = "MD5", .fn = md5authfn },
+};
+
static uint
hash(char *s)
{
@@ -64,7 +108,215 @@
return h % 13;
}
+/* rfc3261 § 10 - Registrations */
+static int
+sip_register(Sip *s, char *user, char *pass)
+{
+ Sipmsg *req, *res;
+ Hdr *h;
+ Biobuf *bin, *bout;
+ char *line, *p, *kv[8], *kv2[2], buf[1024];
+ int n;
+
+ if((bin = Bfdopen(s->fd, OREAD)) == nil)
+ sysfatal("Bfdopen: %r");
+ if((bout = Bfdopen(s->fd, OWRITE)) == nil)
+ sysfatal("Bfdopen: %r");
+
+ /* present yourself */
+ req = newsipmsg();
+ req->method = REGISTER;
+ req->uri = smprint("sip:%s", s->nci->rsys);
+ req->version = sipversion;
+ snprint(buf, sizeof buf, "%s/UDP %s:%s;branch=z9hG4bK703d971c0c737b8e;rport",
+ sipversion, s->nci->lsys, s->nci->lserv);
+ addheader(req, "Via", buf);
+ snprint(buf, sizeof buf, "<sip:%s-0x82a66a010@%s:%s>;expires=3849",
+ user, s->nci->lsys, s->nci->lserv);
+ addheader(req, "Contact", buf);
+ addheader(req, "Max-Forwards", "70");
+ snprint(buf, sizeof buf, "<sip:%s@%s>",
+ user, s->nci->rsys);
+ addheader(req, "To", buf);
+ snprint(buf, sizeof buf, "<sip:%s@%s>;tag=4a5a693256d38cbc",
+ user, s->nci->rsys);
+ addheader(req, "From", buf);
+ addheader(req, "Call-ID", "2cee372fc4be4e45");
+ addheader(req, "CSeq", "16021 REGISTER");
+ addheader(req, "User-Agent", useragent);
+ addheader(req, "Allow", "INVITE,ACK,BYE,CANCEL,OPTIONS,NOTIFY,SUBSCRIBE,INFO,MESSAGE,UPDATE,REFER");
+ snprint(buf, sizeof buf, "%lud", req->len);
+ addheader(req, "Content-Length", buf);
+
+ Bprint(bout, "%S", req);
+ Bflush(bout);
+
+ if(debug)
+ fprint(2, "sent:\n%S\n", req);
+
+ delsipmsg(req);
+
+ /* wait for the challenge */
+ res = newsipmsg();
+ while((line = Brdline(bin, '\n')) != nil){
+ if(strncmp(line, "\r\n", 2) == 0)
+ break;
+
+ p = strchr(line, '\r');
+ *p++ = 0, *p = 0;
+
+ if(strstr(line, ":") == nil && req->code == 0){
+ if(gettokens(line, kv, 3, " ") == 3){
+ res->version = strdup(kv[0]);
+ res->code = strtoul(kv[1], nil, 10);
+ res->reason = strdup(kv[2]);
+ }
+ continue;
+ }
+
+ if(gettokens(line, kv, 2, ": ") == 2)
+ addheader(res, kv[0], kv[1]);
+ }
+
+ if(debug)
+ fprint(2, "rcvd:\n%S\n", res);
+
+ /* respond to the challenge */
+ if((h = getheader(res, "WWW-Authenticate")) == nil)
+ return -1;
+
+ if((n = gettokens(h->value, kv, nelem(kv), ", ")) == 0)
+ return -1;
+
+ while(n-- > 0){
+ if(gettokens(kv[n], kv2, 2, "=\"") != 2)
+ continue;
+
+ /* XXX: this hack should be replaced by a better method */
+ p = strchr(kv2[1], '"');
+ if(p != nil)
+ *p = 0;
+
+ if(strcmp(kv2[0], "algorithm") == 0)
+ req->auth.algo = strdup(kv2[1]);
+ else if(strcmp(kv2[0], "realm") == 0)
+ req->auth.realm = strdup(kv2[1]);
+ else if(strcmp(kv2[0], "nonce") == 0)
+ req->auth.nonce = strdup(kv2[1]);
+ }
+ if(strcmp(res->auth.algo, "MD5") == 0){
+ snprint(buf, sizeof buf, "sip:%s", s->nci->rsys);
+ res->auth.response = algos[AMD5].fn(user, pass, buf, res);
+ if(res->auth.response == nil)
+ return -1;
+ }else
+ return -1;
+
+ req = newsipmsg();
+ req->method = REGISTER;
+ req->uri = strdup(buf);
+ req->version = sipversion;
+ snprint(buf, sizeof buf, "%s/UDP %s:%s;branch=z9hG4bK703d971c0c737b8e;rport",
+ sipversion, s->nci->lsys, s->nci->lserv);
+ addheader(req, "Via", buf);
+ snprint(buf, sizeof buf, "<sip:%s-0x82a66a010@%s:%s>;expires=3849",
+ user, s->nci->lsys, s->nci->lserv);
+ addheader(req, "Contact", buf);
+ snprint(buf, sizeof buf, "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", algorithm=\"%s\"",
+ user, res->auth.realm, res->auth.nonce, req->uri, res->auth.response, res->auth.algo);
+ addheader(req, "Authorization", buf);
+ addheader(req, "Max-Forwards", "70");
+ snprint(buf, sizeof buf, "<sip:%s@%s>",
+ user, s->nci->rsys);
+ addheader(req, "To", buf);
+ snprint(buf, sizeof buf, "<sip:%s@%s>;tag=4a5a693256d38cbc",
+ user, s->nci->rsys);
+ addheader(req, "From", buf);
+ addheader(req, "Call-ID", "2cee372fc4be4e45");
+ addheader(req, "CSeq", "16022 REGISTER");
+ addheader(req, "User-Agent", useragent);
+ addheader(req, "Allow", "INVITE,ACK,BYE,CANCEL,OPTIONS,NOTIFY,SUBSCRIBE,INFO,MESSAGE,UPDATE,REFER");
+ snprint(buf, sizeof buf, "%lud", req->len);
+ addheader(req, "Content-Length", buf);
+
+ Bprint(bout, "%S", req);
+ Bflush(bout);
+
+ if(debug)
+ fprint(2, "sent:\n%S\n", req);
+
+ delsipmsg(res);
+ delsipmsg(req);
+
+ /* get the OK */
+ res = newsipmsg();
+ while((line = Brdline(bin, '\n')) != nil){
+ if(strncmp(line, "\r\n", 2) == 0)
+ break;
+
+ p = strchr(line, '\r');
+ *p++ = 0, *p = 0;
+
+ if(strstr(line, ":") == nil && req->code == 0){
+ if(gettokens(line, kv, 3, " ") == 3){
+ res->version = strdup(kv[0]);
+ res->code = strtoul(kv[1], nil, 10);
+ res->reason = strdup(kv[2]);
+ }
+ continue;
+ }
+
+ if(gettokens(line, kv, 2, ": ") == 2)
+ addheader(res, kv[0], kv[1]);
+ }
+
+ if(debug)
+ fprint(2, "rcvd:\n%S\n", res);
+
+ Bterm(bin);
+ Bterm(bout);
+
+ return 0;
+}
+
+int
+Sfmt(Fmt *f)
+{
+ Sipmsg *m;
+ Hdr *h;
+ int i, n;
+
+ m = va_arg(f->args, Sipmsg*);
+ n = 0;
+
+ if(m->code == 0){ /* request */
+ n += fmtprint(f, "%s %s %s\r\n",
+ getmethodstr(m->method), m->uri, m->version);
+ }else{ /* response */
+ n += fmtprint(f, "%s %d %s\r\n",
+ m->version, m->code, m->reason);
+ }
+
+ for(i = 0; i < nelem(m->headers); i++)
+ for(h = m->headers[i]; h != nil; h = h->next)
+ n += fmtprint(f, "%s: %s\r\n", h->name, h->value);
+ n += fmtprint(f, "\r\n");
+
+ if(m->len > 0){
+ fmtprint(f, "%.*s", (int)m->len, m->body);
+ n += m->len;
+ }
+
+ return n;
+}
+
void
+SIPfmtinstall(void)
+{
+ fmtinstall('S', Sfmt);
+}
+
+void
addheader(Hdrtab *ht, char *name, char *value)
{
Hdr *h, *newh;
@@ -133,53 +385,26 @@
}
}
-/* rfc3261 § 10 - Registrations */
-static int
-sip_register(Sip *s, char *user, char *pass)
+Sipmsg *
+newsipmsg(void)
{
- Biobuf *bin, *bout;
- char *line, *p, *kv[2];
- int n;
+ Sipmsg *m;
- if((bin = Bfdopen(s->fd, OREAD)) == nil)
- sysfatal("Bfdopen: %r");
- if((bout = Bfdopen(s->fd, OWRITE)) == nil)
- sysfatal("Bfdopen: %r");
+ m = emalloc(sizeof(Sipmsg));
+ memset(m, 0, sizeof *m);
- /* present yourself */
- Bprint(bout, registerhdr0, s->nci->rsys, sipversion,
- sipversion, s->nci->lsys, s->nci->lserv,
- user, s->nci->lsys, s->nci->lserv,
- user, s->nci->rsys,
- user, s->nci->rsys);
- Bflush(bout);
+ return m;
+}
- /* wait for the challenge */
- while((line = Brdline(bin, '\n')) != nil){
- if(strncmp(line, "\r\n", 2) == 0)
- break;
-
- p = strchr(line, '\r');
- *p++ = 0, *p = 0;
- if(debug)
- fprint(2, "%s\n", line);
-
- if(strstr(line, ":") == nil)
- continue;
-
- gettokens(line, kv, nelem(kv), ": ");
- if(debug)
- fprint(2, "got key=%s value=%s\n", kv[0], kv[1]);
- }
-
- /* respond to the challenge */
-
- /* get the OK */
-
- Bterm(bin);
- Bterm(bout);
-
- return 0;
+void
+delsipmsg(Sipmsg *m)
+{
+ if(m->uri != nil)
+ free(m->uri);
+ if(m->reason != nil)
+ free(m->reason);
+ delheaders(m);
+ free(m);
}
Sip *