webcookies.c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266
  1. /*
  2. * Cookie file system. Allows hget and multiple webfs's to collaborate.
  3. * Conventionally mounted on /mnt/webcookies.
  4. */
  5. #include <u.h>
  6. #include <libc.h>
  7. #include <bio.h>
  8. #include <ndb.h>
  9. #include <fcall.h>
  10. #include <thread.h>
  11. #include <9p.h>
  12. #include <ctype.h>
  13. int debug = 0;
  14. typedef struct Cookie Cookie;
  15. typedef struct Jar Jar;
  16. struct Cookie
  17. {
  18. /* external info */
  19. char* name;
  20. char* value;
  21. char* dom; /* starts with . */
  22. char* path;
  23. char* version;
  24. char* comment; /* optional, may be nil */
  25. uint expire; /* time of expiration: ~0 means when webcookies dies */
  26. int secure;
  27. int explicitdom; /* dom was explicitly set */
  28. int explicitpath; /* path was explicitly set */
  29. int netscapestyle;
  30. /* internal info */
  31. int deleted;
  32. int mark;
  33. int ondisk;
  34. };
  35. struct Jar
  36. {
  37. Cookie *c;
  38. int nc;
  39. int mc;
  40. Qid qid;
  41. int dirty;
  42. char *file;
  43. char *lockfile;
  44. };
  45. struct {
  46. char *s;
  47. int offset;
  48. int ishttp;
  49. } stab[] = {
  50. "domain", offsetof(Cookie, dom), 1,
  51. "path", offsetof(Cookie, path), 1,
  52. "name", offsetof(Cookie, name), 0,
  53. "value", offsetof(Cookie, value), 0,
  54. "comment", offsetof(Cookie, comment), 1,
  55. "version", offsetof(Cookie, version), 1,
  56. };
  57. struct {
  58. char *s;
  59. int offset;
  60. } itab[] = {
  61. "expire", offsetof(Cookie, expire),
  62. "secure", offsetof(Cookie, secure),
  63. "explicitdomain", offsetof(Cookie, explicitdom),
  64. "explicitpath", offsetof(Cookie, explicitpath),
  65. "netscapestyle", offsetof(Cookie, netscapestyle),
  66. };
  67. #pragma varargck type "J" Jar*
  68. #pragma varargck type "K" Cookie*
  69. /* HTTP format */
  70. int
  71. jarfmt(Fmt *fmt)
  72. {
  73. int i;
  74. Jar *jar;
  75. jar = va_arg(fmt->args, Jar*);
  76. if(jar == nil || jar->nc == 0)
  77. return fmtstrcpy(fmt, "");
  78. fmtprint(fmt, "Cookie: ");
  79. if(jar->c[0].version)
  80. fmtprint(fmt, "$Version=%s; ", jar->c[0].version);
  81. for(i=0; i<jar->nc; i++)
  82. fmtprint(fmt, "%s%s=%s", i ? "; ":"", jar->c[i].name, jar->c[i].value);
  83. fmtprint(fmt, "\r\n");
  84. return 0;
  85. }
  86. /* individual cookie */
  87. int
  88. cookiefmt(Fmt *fmt)
  89. {
  90. int j, k, first;
  91. char *t;
  92. Cookie *c;
  93. c = va_arg(fmt->args, Cookie*);
  94. first = 1;
  95. for(j=0; j<nelem(stab); j++){
  96. t = *(char**)((char*)c+stab[j].offset);
  97. if(t == nil)
  98. continue;
  99. if(first)
  100. first = 0;
  101. else
  102. fmtprint(fmt, " ");
  103. fmtprint(fmt, "%s=%q", stab[j].s, t);
  104. }
  105. for(j=0; j<nelem(itab); j++){
  106. k = *(int*)((char*)c+itab[j].offset);
  107. if(k == 0)
  108. continue;
  109. if(first)
  110. first = 0;
  111. else
  112. fmtprint(fmt, " ");
  113. fmtprint(fmt, "%s=%ud", itab[j].s, k);
  114. }
  115. return 0;
  116. }
  117. /*
  118. * sort cookies:
  119. * - alpha by name
  120. * - alpha by domain
  121. * - longer paths first, then alpha by path (RFC2109 4.3.4)
  122. */
  123. int
  124. cookiecmp(Cookie *a, Cookie *b)
  125. {
  126. int i;
  127. if((i = strcmp(a->name, b->name)) != 0)
  128. return i;
  129. if((i = cistrcmp(a->dom, b->dom)) != 0)
  130. return i;
  131. if((i = strlen(b->path) - strlen(a->path)) != 0)
  132. return i;
  133. if((i = strcmp(a->path, b->path)) != 0)
  134. return i;
  135. return 0;
  136. }
  137. int
  138. exactcookiecmp(Cookie *a, Cookie *b)
  139. {
  140. int i;
  141. if((i = cookiecmp(a, b)) != 0)
  142. return i;
  143. if((i = strcmp(a->value, b->value)) != 0)
  144. return i;
  145. if(a->version || b->version){
  146. if(!a->version)
  147. return -1;
  148. if(!b->version)
  149. return 1;
  150. if((i = strcmp(a->version, b->version)) != 0)
  151. return i;
  152. }
  153. if(a->comment || b->comment){
  154. if(!a->comment)
  155. return -1;
  156. if(!b->comment)
  157. return 1;
  158. if((i = strcmp(a->comment, b->comment)) != 0)
  159. return i;
  160. }
  161. if((i = b->expire - a->expire) != 0)
  162. return i;
  163. if((i = b->secure - a->secure) != 0)
  164. return i;
  165. if((i = b->explicitdom - a->explicitdom) != 0)
  166. return i;
  167. if((i = b->explicitpath - a->explicitpath) != 0)
  168. return i;
  169. if((i = b->netscapestyle - a->netscapestyle) != 0)
  170. return i;
  171. return 0;
  172. }
  173. void
  174. freecookie(Cookie *c)
  175. {
  176. int i;
  177. for(i=0; i<nelem(stab); i++)
  178. free(*(char**)((char*)c+stab[i].offset));
  179. }
  180. void
  181. copycookie(Cookie *c)
  182. {
  183. int i;
  184. char **ps;
  185. for(i=0; i<nelem(stab); i++){
  186. ps = (char**)((char*)c+stab[i].offset);
  187. if(*ps)
  188. *ps = estrdup9p(*ps);
  189. }
  190. }
  191. void
  192. delcookie(Jar *j, Cookie *c)
  193. {
  194. int i;
  195. j->dirty = 1;
  196. i = c - j->c;
  197. if(i < 0 || i >= j->nc)
  198. abort();
  199. c->deleted = 1;
  200. }
  201. void
  202. addcookie(Jar *j, Cookie *c)
  203. {
  204. int i;
  205. if(!c->name || !c->value || !c->path || !c->dom){
  206. fprint(2, "not adding incomplete cookie\n");
  207. return;
  208. }
  209. if(debug)
  210. fprint(2, "add %K\n", c);
  211. for(i=0; i<j->nc; i++)
  212. if(cookiecmp(&j->c[i], c) == 0){
  213. if(debug)
  214. fprint(2, "cookie %K matches %K\n", &j->c[i], c);
  215. if(exactcookiecmp(&j->c[i], c) == 0){
  216. if(debug)
  217. fprint(2, "\texactly\n");
  218. j->c[i].mark = 0;
  219. return;
  220. }
  221. delcookie(j, &j->c[i]);
  222. }
  223. j->dirty = 1;
  224. if(j->nc == j->mc){
  225. j->mc += 16;
  226. j->c = erealloc9p(j->c, j->mc*sizeof(Cookie));
  227. }
  228. j->c[j->nc] = *c;
  229. copycookie(&j->c[j->nc]);
  230. j->nc++;
  231. }
  232. void
  233. purgejar(Jar *j)
  234. {
  235. int i;
  236. for(i=j->nc-1; i>=0; i--){
  237. if(!j->c[i].deleted)
  238. continue;
  239. freecookie(&j->c[i]);
  240. --j->nc;
  241. j->c[i] = j->c[j->nc];
  242. }
  243. }
  244. void
  245. addtojar(Jar *jar, char *line, int ondisk)
  246. {
  247. Cookie c;
  248. int i, j, nf, *pint;
  249. char *f[20], *attr, *val, **pstr;
  250. memset(&c, 0, sizeof c);
  251. c.expire = ~0;
  252. c.ondisk = ondisk;
  253. nf = tokenize(line, f, nelem(f));
  254. for(i=0; i<nf; i++){
  255. attr = f[i];
  256. if((val = strchr(attr, '=')) != nil)
  257. *val++ = '\0';
  258. else
  259. val = "";
  260. /* string attributes */
  261. for(j=0; j<nelem(stab); j++){
  262. if(strcmp(stab[j].s, attr) == 0){
  263. pstr = (char**)((char*)&c+stab[j].offset);
  264. *pstr = val;
  265. }
  266. }
  267. /* integer attributes */
  268. for(j=0; j<nelem(itab); j++){
  269. if(strcmp(itab[j].s, attr) == 0){
  270. pint = (int*)((char*)&c+itab[j].offset);
  271. if(val[0]=='\0')
  272. *pint = 1;
  273. else
  274. *pint = strtoul(val, 0, 0);
  275. }
  276. }
  277. }
  278. if(c.name==nil || c.value==nil || c.dom==nil || c.path==nil){
  279. if(debug)
  280. fprint(2, "ignoring fractional cookie %K\n", &c);
  281. return;
  282. }
  283. addcookie(jar, &c);
  284. }
  285. Jar*
  286. newjar(void)
  287. {
  288. Jar *jar;
  289. jar = emalloc9p(sizeof(Jar));
  290. return jar;
  291. }
  292. int
  293. expirejar(Jar *jar, int exiting)
  294. {
  295. int i, n;
  296. uint now;
  297. now = time(0);
  298. n = 0;
  299. for(i=0; i<jar->nc; i++){
  300. if(jar->c[i].expire < now || (exiting && jar->c[i].expire==~0)){
  301. delcookie(jar, &jar->c[i]);
  302. n++;
  303. }
  304. }
  305. return n;
  306. }
  307. int
  308. syncjar(Jar *jar)
  309. {
  310. int i, fd;
  311. char *line;
  312. Dir *d;
  313. Biobuf *b;
  314. Qid q;
  315. if(jar->file==nil)
  316. return 0;
  317. memset(&q, 0, sizeof q);
  318. if((d = dirstat(jar->file)) != nil){
  319. q = d->qid;
  320. if(d->qid.path != jar->qid.path || d->qid.vers != jar->qid.vers)
  321. jar->dirty = 1;
  322. free(d);
  323. }
  324. if(jar->dirty == 0)
  325. return 0;
  326. fd = -1;
  327. for(i=0; i<50; i++){
  328. if((fd = create(jar->lockfile, OWRITE, DMEXCL|0666)) < 0){
  329. sleep(100);
  330. continue;
  331. }
  332. break;
  333. }
  334. if(fd < 0){
  335. if(debug)
  336. fprint(2, "open %s: %r", jar->lockfile);
  337. werrstr("cannot acquire jar lock: %r");
  338. return -1;
  339. }
  340. for(i=0; i<jar->nc; i++) /* mark is cleared by addcookie */
  341. jar->c[i].mark = jar->c[i].ondisk;
  342. if((b = Bopen(jar->file, OREAD)) == nil){
  343. if(debug)
  344. fprint(2, "Bopen %s: %r", jar->file);
  345. werrstr("cannot read cookie file %s: %r", jar->file);
  346. close(fd);
  347. return -1;
  348. }
  349. for(; (line = Brdstr(b, '\n', 1)) != nil; free(line)){
  350. if(*line == '#')
  351. continue;
  352. addtojar(jar, line, 1);
  353. }
  354. Bterm(b);
  355. for(i=0; i<jar->nc; i++)
  356. if(jar->c[i].mark)
  357. delcookie(jar, &jar->c[i]);
  358. purgejar(jar);
  359. b = Bopen(jar->file, OWRITE);
  360. if(b == nil){
  361. if(debug)
  362. fprint(2, "Bopen write %s: %r", jar->file);
  363. close(fd);
  364. return -1;
  365. }
  366. Bprint(b, "# webcookies cookie jar\n");
  367. Bprint(b, "# comments and non-standard fields will be lost\n");
  368. for(i=0; i<jar->nc; i++){
  369. if(jar->c[i].expire == ~0)
  370. continue;
  371. Bprint(b, "%K\n", &jar->c[i]);
  372. jar->c[i].ondisk = 1;
  373. }
  374. Bterm(b);
  375. jar->dirty = 0;
  376. close(fd);
  377. if((d = dirstat(jar->file)) != nil){
  378. jar->qid = d->qid;
  379. free(d);
  380. }
  381. return 0;
  382. }
  383. Jar*
  384. readjar(char *file)
  385. {
  386. char *lock, *p;
  387. Jar *jar;
  388. jar = newjar();
  389. lock = emalloc9p(strlen(file)+10);
  390. strcpy(lock, file);
  391. if((p = strrchr(lock, '/')) != nil)
  392. p++;
  393. else
  394. p = lock;
  395. memmove(p+2, p, strlen(p)+1);
  396. p[0] = 'L';
  397. p[1] = '.';
  398. jar->lockfile = lock;
  399. jar->file = file;
  400. jar->dirty = 1;
  401. if(syncjar(jar) < 0){
  402. free(jar->file);
  403. free(jar->lockfile);
  404. free(jar);
  405. return nil;
  406. }
  407. return jar;
  408. }
  409. void
  410. closejar(Jar *jar)
  411. {
  412. int i;
  413. expirejar(jar, 0);
  414. if(syncjar(jar) < 0)
  415. fprint(2, "warning: cannot rewrite cookie jar: %r\n");
  416. for(i=0; i<jar->nc; i++)
  417. freecookie(&jar->c[i]);
  418. free(jar->file);
  419. free(jar);
  420. }
  421. /*
  422. * Domain name matching is per RFC2109, section 2:
  423. *
  424. * Hosts names can be specified either as an IP address or a FQHN
  425. * string. Sometimes we compare one host name with another. Host A's
  426. * name domain-matches host B's if
  427. *
  428. * * both host names are IP addresses and their host name strings match
  429. * exactly; or
  430. *
  431. * * both host names are FQDN strings and their host name strings match
  432. * exactly; or
  433. *
  434. * * A is a FQDN string and has the form NB, where N is a non-empty name
  435. * string, B has the form .B', and B' is a FQDN string. (So, x.y.com
  436. * domain-matches .y.com but not y.com.)
  437. *
  438. * Note that domain-match is not a commutative operation: a.b.c.com
  439. * domain-matches .c.com, but not the reverse.
  440. *
  441. * (This does not verify that IP addresses and FQDN's are well-formed.)
  442. */
  443. int
  444. isdomainmatch(char *name, char *pattern)
  445. {
  446. int lname, lpattern;
  447. if(cistrcmp(name, pattern)==0)
  448. return 1;
  449. if(strcmp(ipattr(name), "dom")==0 && pattern[0]=='.'){
  450. lname = strlen(name);
  451. lpattern = strlen(pattern);
  452. if(lname >= lpattern && cistrcmp(name+lname-lpattern, pattern)==0)
  453. return 1;
  454. }
  455. return 0;
  456. }
  457. /*
  458. * RFC2109 4.3.4:
  459. * - domain must match
  460. * - path in cookie must be a prefix of request path
  461. * - cookie must not have expired
  462. */
  463. int
  464. iscookiematch(Cookie *c, char *dom, char *path, uint now)
  465. {
  466. return isdomainmatch(dom, c->dom)
  467. && strncmp(c->path, path, strlen(c->path))==0
  468. && c->expire >= now;
  469. }
  470. /*
  471. * Produce a subjar of matching cookies.
  472. * Secure cookies are only included if secure is set.
  473. */
  474. Jar*
  475. cookiesearch(Jar *jar, char *dom, char *path, int issecure)
  476. {
  477. int i;
  478. Jar *j;
  479. uint now;
  480. now = time(0);
  481. j = newjar();
  482. for(i=0; i<jar->nc; i++)
  483. if((issecure || !jar->c[i].secure) && iscookiematch(&jar->c[i], dom, path, now))
  484. addcookie(j, &jar->c[i]);
  485. if(j->nc == 0){
  486. closejar(j);
  487. werrstr("no cookies found");
  488. return nil;
  489. }
  490. qsort(j->c, j->nc, sizeof(j->c[0]), (int(*)(const void*, const void*))cookiecmp);
  491. return j;
  492. }
  493. /*
  494. * RFC2109 4.3.2 security checks
  495. */
  496. char*
  497. isbadcookie(Cookie *c, char *dom, char *path)
  498. {
  499. if(strncmp(c->path, path, strlen(c->path)) != 0)
  500. return "cookie path is not a prefix of the request path";
  501. if(c->explicitdom && c->dom[0] != '.')
  502. return "cookie domain doesn't start with dot";
  503. if(memchr(c->dom+1, '.', strlen(c->dom)-1-1) == nil)
  504. return "cookie domain doesn't have embedded dots";
  505. if(!isdomainmatch(dom, c->dom))
  506. return "request host does not match cookie domain";
  507. if(strcmp(ipattr(dom), "dom")==0
  508. && memchr(dom, '.', strlen(dom)-strlen(c->dom)) != nil)
  509. return "request host contains dots before cookie domain";
  510. return 0;
  511. }
  512. /*
  513. * Sunday, 25-Jan-2002 12:24:36 GMT
  514. * Sunday, 25 Jan 2002 12:24:36 GMT
  515. * Sun, 25 Jan 02 12:24:36 GMT
  516. */
  517. int
  518. isleap(int year)
  519. {
  520. return year%4==0 && (year%100!=0 || year%400==0);
  521. }
  522. uint
  523. strtotime(char *s)
  524. {
  525. char *os;
  526. int i;
  527. Tm tm;
  528. static int mday[2][12] = {
  529. 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
  530. 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
  531. };
  532. static char *wday[] = {
  533. "Sunday", "Monday", "Tuesday", "Wednesday",
  534. "Thursday", "Friday", "Saturday",
  535. };
  536. static char *mon[] = {
  537. "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  538. "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
  539. };
  540. os = s;
  541. /* Sunday, */
  542. for(i=0; i<nelem(wday); i++){
  543. if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){
  544. s += strlen(wday[i]);
  545. break;
  546. }
  547. if(cistrncmp(s, wday[i], 3) == 0){
  548. s += 3;
  549. break;
  550. }
  551. }
  552. if(i==nelem(wday)){
  553. if(debug)
  554. fprint(2, "bad wday (%s)\n", os);
  555. return -1;
  556. }
  557. if(*s++ != ',' || *s++ != ' '){
  558. if(debug)
  559. fprint(2, "bad wday separator (%s)\n", os);
  560. return -1;
  561. }
  562. /* 25- */
  563. if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' ')){
  564. if(debug)
  565. fprint(2, "bad day of month (%s)\n", os);
  566. return -1;
  567. }
  568. tm.mday = strtol(s, 0, 10);
  569. s += 3;
  570. /* Jan- */
  571. for(i=0; i<nelem(mon); i++)
  572. if(cistrncmp(s, mon[i], 3) == 0){
  573. tm.mon = i;
  574. s += 3;
  575. break;
  576. }
  577. if(i==nelem(mon)){
  578. if(debug)
  579. fprint(2, "bad month (%s)\n", os);
  580. return -1;
  581. }
  582. if(s[0] != '-' && s[0] != ' '){
  583. if(debug)
  584. fprint(2, "bad month separator (%s)\n", os);
  585. return -1;
  586. }
  587. s++;
  588. /* 2002 */
  589. if(!isdigit(s[0]) || !isdigit(s[1])){
  590. if(debug)
  591. fprint(2, "bad year (%s)\n", os);
  592. return -1;
  593. }
  594. tm.year = strtol(s, 0, 10);
  595. s += 2;
  596. if(isdigit(s[0]) && isdigit(s[1]))
  597. s += 2;
  598. else{
  599. if(tm.year <= 68)
  600. tm.year += 2000;
  601. else
  602. tm.year += 1900;
  603. }
  604. if(tm.mday==0 || tm.mday > mday[isleap(tm.year)][tm.mon]){
  605. if(debug)
  606. fprint(2, "invalid day of month (%s)\n", os);
  607. return -1;
  608. }
  609. tm.year -= 1900;
  610. if(*s++ != ' '){
  611. if(debug)
  612. fprint(2, "bad year separator (%s)\n", os);
  613. return -1;
  614. }
  615. if(!isdigit(s[0]) || !isdigit(s[1]) || s[2]!=':'
  616. || !isdigit(s[3]) || !isdigit(s[4]) || s[5]!=':'
  617. || !isdigit(s[6]) || !isdigit(s[7]) || s[8]!=' '){
  618. if(debug)
  619. fprint(2, "bad time (%s)\n", os);
  620. return -1;
  621. }
  622. tm.hour = atoi(s);
  623. tm.min = atoi(s+3);
  624. tm.sec = atoi(s+6);
  625. if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60){
  626. if(debug)
  627. fprint(2, "invalid time (%s)\n", os);
  628. return -1;
  629. }
  630. s += 9;
  631. if(cistrcmp(s, "GMT") != 0){
  632. if(debug)
  633. fprint(2, "time zone not GMT (%s)\n", os);
  634. return -1;
  635. }
  636. strcpy(tm.zone, "GMT");
  637. tm.yday = 0;
  638. return tm2sec(&tm);
  639. }
  640. /*
  641. * skip linear whitespace. we're a bit more lenient than RFC2616 2.2.
  642. */
  643. char*
  644. skipspace(char *s)
  645. {
  646. while(*s=='\r' || *s=='\n' || *s==' ' || *s=='\t')
  647. s++;
  648. return s;
  649. }
  650. /*
  651. * Try to identify old netscape headers.
  652. * The old headers:
  653. * - didn't allow spaces around the '='
  654. * - used an 'Expires' attribute
  655. * - had no 'Version' attribute
  656. * - had no quotes
  657. * - allowed whitespace in values
  658. * - apparently separated attr/value pairs with ';' exclusively
  659. */
  660. int
  661. isnetscape(char *hdr)
  662. {
  663. char *s;
  664. for(s=hdr; (s=strchr(s, '=')) != nil; s++){
  665. if(isspace(s[1]) || (s > hdr && isspace(s[-1])))
  666. return 0;
  667. if(s[1]=='"')
  668. return 0;
  669. }
  670. if(cistrstr(hdr, "version="))
  671. return 0;
  672. return 1;
  673. }
  674. /*
  675. * Parse HTTP response headers, adding cookies to jar.
  676. * Overwrites the headers. May overwrite path.
  677. */
  678. char* parsecookie(Cookie*, char*, char**, int, char*, char*);
  679. int
  680. parsehttp(Jar *jar, char *hdr, char *dom, char *path)
  681. {
  682. static char setcookie[] = "Set-Cookie:";
  683. char *e, *p, *nextp;
  684. Cookie c;
  685. int isns, n;
  686. isns = isnetscape(hdr);
  687. n = 0;
  688. for(p=hdr; p; p=nextp){
  689. p = skipspace(p);
  690. if(*p == '\0')
  691. break;
  692. nextp = strchr(p, '\n');
  693. if(nextp != nil)
  694. *nextp++ = '\0';
  695. if(debug)
  696. fprint(2, "?%s\n", p);
  697. if(cistrncmp(p, setcookie, strlen(setcookie)) != 0)
  698. continue;
  699. if(debug)
  700. fprint(2, "%s\n", p);
  701. p = skipspace(p+strlen(setcookie));
  702. for(; *p; p=skipspace(p)){
  703. if((e = parsecookie(&c, p, &p, isns, dom, path)) != nil){
  704. if(debug)
  705. fprint(2, "parse cookie: %s\n", e);
  706. break;
  707. }
  708. if((e = isbadcookie(&c, dom, path)) != nil){
  709. if(debug)
  710. fprint(2, "reject cookie; %s\n", e);
  711. continue;
  712. }
  713. addcookie(jar, &c);
  714. n++;
  715. }
  716. }
  717. return n;
  718. }
  719. static char*
  720. skipquoted(char *s)
  721. {
  722. /*
  723. * Sec 2.2 of RFC2616 defines a "quoted-string" as:
  724. *
  725. * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
  726. * qdtext = <any TEXT except <">>
  727. * quoted-pair = "\" CHAR
  728. *
  729. * TEXT is any octet except CTLs, but including LWS;
  730. * LWS is [CR LF] 1*(SP | HT);
  731. * CHARs are ASCII octets 0-127; (NOTE: we reject 0's)
  732. * CTLs are octets 0-31 and 127;
  733. */
  734. if(*s != '"')
  735. return s;
  736. for(s++; 32 <= *s && *s < 127 && *s != '"'; s++)
  737. if(*s == '\\' && *(s+1) != '\0')
  738. s++;
  739. return s;
  740. }
  741. static char*
  742. skiptoken(char *s)
  743. {
  744. /*
  745. * Sec 2.2 of RFC2616 defines a "token" as
  746. * 1*<any CHAR except CTLs or separators>;
  747. * CHARs are ASCII octets 0-127;
  748. * CTLs are octets 0-31 and 127;
  749. * separators are "()<>@,;:\/[]?={}", double-quote, SP (32), and HT (9)
  750. */
  751. while(32 <= *s && *s < 127 && strchr("()<>@,;:[]?={}\" \t\\", *s)==nil)
  752. s++;
  753. return s;
  754. }
  755. static char*
  756. skipvalue(char *s, int isns)
  757. {
  758. char *t;
  759. /*
  760. * An RFC2109 value is an HTTP token or an HTTP quoted string.
  761. * Netscape servers ignore the spec and rely on semicolons, apparently.
  762. */
  763. if(isns){
  764. if((t = strchr(s, ';')) == nil)
  765. t = s+strlen(s);
  766. return t;
  767. }
  768. if(*s == '"')
  769. return skipquoted(s);
  770. return skiptoken(s);
  771. }
  772. /*
  773. * RMID=80b186bb64c03c65fab767f8; expires=Monday, 10-Feb-2003 04:44:39 GMT;
  774. * path=/; domain=.nytimes.com
  775. */
  776. char*
  777. parsecookie(Cookie *c, char *p, char **e, int isns, char *dom, char *path)
  778. {
  779. int i, done;
  780. char *t, *u, *attr, *val;
  781. memset(c, 0, sizeof *c);
  782. c->expire = ~0;
  783. /* NAME=VALUE */
  784. t = skiptoken(p);
  785. c->name = p;
  786. p = skipspace(t);
  787. if(*p != '='){
  788. Badname:
  789. return "malformed cookie: no NAME=VALUE";
  790. }
  791. *t = '\0';
  792. p = skipspace(p+1);
  793. t = skipvalue(p, isns);
  794. if(*t)
  795. *t++ = '\0';
  796. c->value = p;
  797. p = skipspace(t);
  798. if(c->name[0]=='\0' || c->value[0]=='\0')
  799. goto Badname;
  800. done = 0;
  801. for(; *p && !done; p=skipspace(p)){
  802. attr = p;
  803. t = skiptoken(p);
  804. u = skipspace(t);
  805. switch(*u){
  806. case '\0':
  807. *t = '\0';
  808. p = val = u;
  809. break;
  810. case ';':
  811. *t = '\0';
  812. val = "";
  813. p = u+1;
  814. break;
  815. case '=':
  816. *t = '\0';
  817. val = skipspace(u+1);
  818. p = skipvalue(val, isns);
  819. if(*p==',')
  820. done = 1;
  821. if(*p)
  822. *p++ = '\0';
  823. break;
  824. case ',':
  825. if(!isns){
  826. val = "";
  827. p = u;
  828. *p++ = '\0';
  829. done = 1;
  830. break;
  831. }
  832. default:
  833. if(debug)
  834. fprint(2, "syntax: %s\n", p);
  835. return "syntax error";
  836. }
  837. for(i=0; i<nelem(stab); i++)
  838. if(stab[i].ishttp && cistrcmp(stab[i].s, attr)==0)
  839. *(char**)((char*)c+stab[i].offset) = val;
  840. if(cistrcmp(attr, "expires") == 0){
  841. if(!isns)
  842. return "non-netscape cookie has Expires tag";
  843. if(!val[0])
  844. return "bad expires tag";
  845. c->expire = strtotime(val);
  846. if(c->expire == ~0)
  847. return "cannot parse netscape expires tag";
  848. }
  849. if(cistrcmp(attr, "max-age") == 0)
  850. c->expire = time(0)+atoi(val);
  851. if(cistrcmp(attr, "secure") == 0)
  852. c->secure = 1;
  853. }
  854. if(c->dom)
  855. c->explicitdom = 1;
  856. else
  857. c->dom = dom;
  858. if(c->path)
  859. c->explicitpath = 1;
  860. else{
  861. c->path = path;
  862. if((t = strchr(c->path, '?')) != 0)
  863. *t = '\0';
  864. if((t = strrchr(c->path, '/')) != 0)
  865. *t = '\0';
  866. }
  867. c->netscapestyle = isns;
  868. *e = p;
  869. return nil;
  870. }
  871. Jar *jar;
  872. enum
  873. {
  874. Xhttp = 1,
  875. Xcookies,
  876. NeedUrl = 0,
  877. HaveUrl,
  878. };
  879. typedef struct Aux Aux;
  880. struct Aux
  881. {
  882. int state;
  883. char *dom;
  884. char *path;
  885. char *inhttp;
  886. char *outhttp;
  887. char *ctext;
  888. int rdoff;
  889. };
  890. enum
  891. {
  892. AuxBuf = 4096,
  893. MaxCtext = 16*1024*1024,
  894. };
  895. void
  896. fsopen(Req *r)
  897. {
  898. char *s, *es;
  899. int i, sz;
  900. Aux *a;
  901. switch((uintptr)r->fid->file->aux){
  902. case Xhttp:
  903. syncjar(jar);
  904. a = emalloc9p(sizeof(Aux));
  905. r->fid->aux = a;
  906. a->inhttp = emalloc9p(AuxBuf);
  907. a->outhttp = emalloc9p(AuxBuf);
  908. break;
  909. case Xcookies:
  910. syncjar(jar);
  911. a = emalloc9p(sizeof(Aux));
  912. r->fid->aux = a;
  913. if(r->ifcall.mode&OTRUNC){
  914. a->ctext = emalloc9p(1);
  915. a->ctext[0] = '\0';
  916. }else{
  917. sz = 256*jar->nc+1024; /* BUG should do better */
  918. a->ctext = emalloc9p(sz);
  919. a->ctext[0] = '\0';
  920. s = a->ctext;
  921. es = s+sz;
  922. for(i=0; i<jar->nc; i++)
  923. s = seprint(s, es, "%K\n", &jar->c[i]);
  924. }
  925. break;
  926. }
  927. respond(r, nil);
  928. }
  929. void
  930. fsread(Req *r)
  931. {
  932. Aux *a;
  933. a = r->fid->aux;
  934. switch((uintptr)r->fid->file->aux){
  935. case Xhttp:
  936. if(a->state == NeedUrl){
  937. respond(r, "must write url before read");
  938. return;
  939. }
  940. r->ifcall.offset = a->rdoff;
  941. readstr(r, a->outhttp);
  942. a->rdoff += r->ofcall.count;
  943. respond(r, nil);
  944. return;
  945. case Xcookies:
  946. readstr(r, a->ctext);
  947. respond(r, nil);
  948. return;
  949. default:
  950. respond(r, "bug in webcookies");
  951. return;
  952. }
  953. }
  954. void
  955. fswrite(Req *r)
  956. {
  957. Aux *a;
  958. int i, sz, hlen, issecure;
  959. char buf[1024], *p;
  960. Jar *j;
  961. a = r->fid->aux;
  962. switch((uintptr)r->fid->file->aux){
  963. case Xhttp:
  964. if(a->state == NeedUrl){
  965. if(r->ifcall.count >= sizeof buf){
  966. respond(r, "url too long");
  967. return;
  968. }
  969. memmove(buf, r->ifcall.data, r->ifcall.count);
  970. buf[r->ifcall.count] = '\0';
  971. issecure = 0;
  972. if(cistrncmp(buf, "http://", 7) == 0)
  973. hlen = 7;
  974. else if(cistrncmp(buf, "https://", 8) == 0){
  975. hlen = 8;
  976. issecure = 1;
  977. }else{
  978. respond(r, "url must begin http:// or https://");
  979. return;
  980. }
  981. if(buf[hlen]=='/'){
  982. respond(r, "url without host name");
  983. return;
  984. }
  985. p = strchr(buf+hlen, '/');
  986. if(p == nil)
  987. a->path = estrdup9p("/");
  988. else{
  989. a->path = estrdup9p(p);
  990. *p = '\0';
  991. }
  992. a->dom = estrdup9p(buf+hlen);
  993. a->state = HaveUrl;
  994. j = cookiesearch(jar, a->dom, a->path, issecure);
  995. if(debug){
  996. fprint(2, "search %s %s got %p\n", a->dom, a->path, j);
  997. if(j){
  998. fprint(2, "%d cookies\n", j->nc);
  999. for(i=0; i<j->nc; i++)
  1000. fprint(2, "%K\n", &j->c[i]);
  1001. }
  1002. }
  1003. snprint(a->outhttp, AuxBuf, "%J", j);
  1004. if(j)
  1005. closejar(j);
  1006. }else{
  1007. if(strlen(a->inhttp)+r->ifcall.count >= AuxBuf){
  1008. respond(r, "http headers too large");
  1009. return;
  1010. }
  1011. memmove(a->inhttp+strlen(a->inhttp), r->ifcall.data, r->ifcall.count);
  1012. }
  1013. r->ofcall.count = r->ifcall.count;
  1014. respond(r, nil);
  1015. return;
  1016. case Xcookies:
  1017. sz = r->ifcall.count+r->ifcall.offset;
  1018. if(sz > strlen(a->ctext)){
  1019. if(sz >= MaxCtext){
  1020. respond(r, "cookie file too large");
  1021. return;
  1022. }
  1023. a->ctext = erealloc9p(a->ctext, sz+1);
  1024. a->ctext[sz] = '\0';
  1025. }
  1026. memmove(a->ctext+r->ifcall.offset, r->ifcall.data, r->ifcall.count);
  1027. r->ofcall.count = r->ifcall.count;
  1028. respond(r, nil);
  1029. return;
  1030. default:
  1031. respond(r, "bug in webcookies");
  1032. return;
  1033. }
  1034. }
  1035. void
  1036. fsdestroyfid(Fid *fid)
  1037. {
  1038. char *p, *nextp;
  1039. Aux *a;
  1040. int i;
  1041. a = fid->aux;
  1042. if(a == nil)
  1043. return;
  1044. switch((uintptr)fid->file->aux){
  1045. case Xhttp:
  1046. parsehttp(jar, a->inhttp, a->dom, a->path);
  1047. break;
  1048. case Xcookies:
  1049. for(i=0; i<jar->nc; i++)
  1050. jar->c[i].mark = 1;
  1051. for(p=a->ctext; *p; p=nextp){
  1052. if((nextp = strchr(p, '\n')) != nil)
  1053. *nextp++ = '\0';
  1054. else
  1055. nextp = "";
  1056. addtojar(jar, p, 0);
  1057. }
  1058. for(i=0; i<jar->nc; i++)
  1059. if(jar->c[i].mark)
  1060. delcookie(jar, &jar->c[i]);
  1061. break;
  1062. }
  1063. syncjar(jar);
  1064. free(a->dom);
  1065. free(a->path);
  1066. free(a->inhttp);
  1067. free(a->outhttp);
  1068. free(a->ctext);
  1069. free(a);
  1070. }
  1071. void
  1072. fsend(Srv*)
  1073. {
  1074. closejar(jar);
  1075. }
  1076. Srv fs =
  1077. {
  1078. .open= fsopen,
  1079. .read= fsread,
  1080. .write= fswrite,
  1081. .destroyfid= fsdestroyfid,
  1082. .end= fsend,
  1083. };
  1084. void
  1085. usage(void)
  1086. {
  1087. fprint(2, "usage: webcookies [-f file] [-m mtpt] [-s service]\n");
  1088. exits("usage");
  1089. }
  1090. void
  1091. main(int argc, char **argv)
  1092. {
  1093. char *file, *mtpt, *home, *srv;
  1094. file = nil;
  1095. srv = nil;
  1096. mtpt = "/mnt/webcookies";
  1097. ARGBEGIN{
  1098. case 'D':
  1099. chatty9p++;
  1100. break;
  1101. case 'd':
  1102. debug = 1;
  1103. break;
  1104. case 'f':
  1105. file = EARGF(usage());
  1106. break;
  1107. case 's':
  1108. srv = EARGF(usage());
  1109. break;
  1110. case 'm':
  1111. mtpt = EARGF(usage());
  1112. break;
  1113. default:
  1114. usage();
  1115. }ARGEND
  1116. if(argc != 0)
  1117. usage();
  1118. quotefmtinstall();
  1119. fmtinstall('J', jarfmt);
  1120. fmtinstall('K', cookiefmt);
  1121. if(file == nil){
  1122. home = getenv("home");
  1123. if(home == nil)
  1124. sysfatal("no cookie file specified and no $home");
  1125. file = emalloc9p(strlen(home)+30);
  1126. strcpy(file, home);
  1127. strcat(file, "/lib/webcookies");
  1128. }
  1129. if(access(file, AEXIST) < 0)
  1130. close(create(file, OWRITE, 0666));
  1131. jar = readjar(file);
  1132. if(jar == nil)
  1133. sysfatal("readjar: %r");
  1134. fs.tree = alloctree("cookie", "cookie", DMDIR|0555, nil);
  1135. closefile(createfile(fs.tree->root, "http", "cookie", 0666, (void*)Xhttp));
  1136. closefile(createfile(fs.tree->root, "cookies", "cookie", 0666, (void*)Xcookies));
  1137. postmountsrv(&fs, srv, mtpt, MREPL);
  1138. exits(nil);
  1139. }