1 #include "common.h"
2 #include <ctype.h>
3 #include <plumb.h>
4 #include <libsec.h>
5 #include <auth.h>
6 #include "dat.h"
7
8 #pragma varargck argpos imap4cmd 2
9 #pragma varargck type "Z" char*
10
11 int doublequote(Fmt*);
12 int pipeline = 1;
13
14 static char Eio[] = "i/o error";
15
16 typedef struct Imap Imap;
17 struct Imap {
18 char *freep; // free this to free the strings below
19
20 char *host;
21 char *user;
22 char *mbox;
23
24 int mustssl;
25 int refreshtime;
26 int debug;
27
28 ulong tag;
29 ulong validity;
30 int nmsg;
31 int size;
32 char *base;
33 char *data;
34
35 vlong *uid;
36 int nuid;
37 int muid;
38
39 Thumbprint *thumb;
40
41 // open network connection
42 Biobuf bin;
43 Biobuf bout;
44 int fd;
45 };
46
47 static char*
removecr(char * s)48 removecr(char *s)
49 {
50 char *r, *w;
51
52 for(r=w=s; *r; r++)
53 if(*r != '\r')
54 *w++ = *r;
55 *w = '\0';
56 return s;
57 }
58
59 //
60 // send imap4 command
61 //
62 static void
imap4cmd(Imap * imap,char * fmt,...)63 imap4cmd(Imap *imap, char *fmt, ...)
64 {
65 char buf[128], *p;
66 va_list va;
67
68 va_start(va, fmt);
69 p = buf+sprint(buf, "9X%lud ", imap->tag);
70 vseprint(p, buf+sizeof(buf), fmt, va);
71 va_end(va);
72
73 p = buf+strlen(buf);
74 if(p > (buf+sizeof(buf)-3))
75 sysfatal("imap4 command too long");
76
77 if(imap->debug)
78 fprint(2, "-> %s\n", buf);
79 strcpy(p, "\r\n");
80 Bwrite(&imap->bout, buf, strlen(buf));
81 Bflush(&imap->bout);
82 }
83
84 enum {
85 OK,
86 NO,
87 BAD,
88 BYE,
89 EXISTS,
90 STATUS,
91 FETCH,
92 UNKNOWN,
93 };
94
95 static char *verblist[] = {
96 [OK] "OK",
97 [NO] "NO",
98 [BAD] "BAD",
99 [BYE] "BYE",
100 [EXISTS] "EXISTS",
101 [STATUS] "STATUS",
102 [FETCH] "FETCH",
103 };
104
105 static int
verbcode(char * verb)106 verbcode(char *verb)
107 {
108 int i;
109 char *q;
110
111 if(q = strchr(verb, ' '))
112 *q = '\0';
113
114 for(i=0; i<nelem(verblist); i++)
115 if(verblist[i] && strcmp(verblist[i], verb)==0){
116 if(q)
117 *q = ' ';
118 return i;
119 }
120 if(q)
121 *q = ' ';
122 return UNKNOWN;
123 }
124
125 static void
strupr(char * s)126 strupr(char *s)
127 {
128 for(; *s; s++)
129 if('a' <= *s && *s <= 'z')
130 *s += 'A'-'a';
131 }
132
133 static void
imapgrow(Imap * imap,int n)134 imapgrow(Imap *imap, int n)
135 {
136 int i;
137
138 if(imap->data == nil){
139 imap->base = emalloc(n+1);
140 imap->data = imap->base;
141 imap->size = n+1;
142 }
143 if(n >= imap->size){
144 // friggin microsoft - reallocate
145 i = imap->data - imap->base;
146 imap->base = erealloc(imap->base, i+n+1);
147 imap->data = imap->base + i;
148 imap->size = n+1;
149 }
150 }
151
152
153 //
154 // get imap4 response line. there might be various
155 // data or other informational lines mixed in.
156 //
157 static char*
imap4resp(Imap * imap)158 imap4resp(Imap *imap)
159 {
160 char *line, *p, *ep, *op, *q, *r, *en, *verb;
161 int i, n;
162 static char error[256];
163
164 while(p = Brdline(&imap->bin, '\n')){
165 ep = p+Blinelen(&imap->bin);
166 while(ep > p && (ep[-1]=='\n' || ep[-1]=='\r'))
167 *--ep = '\0';
168
169 if(imap->debug)
170 fprint(2, "<- %s\n", p);
171 strupr(p);
172
173 switch(p[0]){
174 case '+':
175 if(imap->tag == 0)
176 fprint(2, "unexpected: %s\n", p);
177 break;
178
179 // ``unsolicited'' information; everything happens here.
180 case '*':
181 if(p[1]!=' ')
182 continue;
183 p += 2;
184 line = p;
185 n = strtol(p, &p, 10);
186 if(*p==' ')
187 p++;
188 verb = p;
189
190 if(p = strchr(verb, ' '))
191 p++;
192 else
193 p = verb+strlen(verb);
194
195 switch(verbcode(verb)){
196 case OK:
197 case NO:
198 case BAD:
199 // human readable text at p;
200 break;
201 case BYE:
202 // early disconnect
203 // human readable text at p;
204 break;
205
206 // * 32 EXISTS
207 case EXISTS:
208 imap->nmsg = n;
209 break;
210
211 // * STATUS Inbox (MESSAGES 2 UIDVALIDITY 960164964)
212 case STATUS:
213 if(q = strstr(p, "MESSAGES"))
214 imap->nmsg = atoi(q+8);
215 if(q = strstr(p, "UIDVALIDITY"))
216 imap->validity = strtoul(q+11, 0, 10);
217 break;
218
219 case FETCH:
220 // * 1 FETCH (uid 8889 RFC822.SIZE 3031 body[] {3031}
221 // <3031 bytes of data>
222 // )
223 if(strstr(p, "RFC822.SIZE") && strstr(p, "BODY[]")){
224 if((q = strchr(p, '{'))
225 && (n=strtol(q+1, &en, 0), *en=='}')){
226 if(imap->data == nil || n >= imap->size)
227 imapgrow(imap, n);
228 if((i = Bread(&imap->bin, imap->data, n)) != n){
229 snprint(error, sizeof error,
230 "short read %d != %d: %r\n",
231 i, n);
232 return error;
233 }
234 if(imap->debug)
235 fprint(2, "<- read %d bytes\n", n);
236 imap->data[n] = '\0';
237 if(imap->debug)
238 fprint(2, "<- %s\n", imap->data);
239 imap->data += n;
240 imap->size -= n;
241 p = Brdline(&imap->bin, '\n');
242 if(imap->debug)
243 fprint(2, "<- ignoring %.*s\n",
244 Blinelen(&imap->bin), p);
245 }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
246 *r = '\0';
247 q++;
248 n = r-q;
249 if(imap->data == nil || n >= imap->size)
250 imapgrow(imap, n);
251 memmove(imap->data, q, n);
252 imap->data[n] = '\0';
253 imap->data += n;
254 imap->size -= n;
255 }else
256 return "confused about FETCH response";
257 break;
258 }
259
260 // * 1 FETCH (UID 1 RFC822.SIZE 511)
261 if(q=strstr(p, "RFC822.SIZE")){
262 imap->size = atoi(q+11);
263 break;
264 }
265
266 // * 1 FETCH (UID 1 RFC822.HEADER {496}
267 // <496 bytes of data>
268 // )
269 // * 1 FETCH (UID 1 RFC822.HEADER "data")
270 if(strstr(p, "RFC822.HEADER") || strstr(p, "RFC822.TEXT")){
271 if((q = strchr(p, '{'))
272 && (n=strtol(q+1, &en, 0), *en=='}')){
273 if(imap->data == nil || n >= imap->size)
274 imapgrow(imap, n);
275 if((i = Bread(&imap->bin, imap->data, n)) != n){
276 snprint(error, sizeof error,
277 "short read %d != %d: %r\n",
278 i, n);
279 return error;
280 }
281 if(imap->debug)
282 fprint(2, "<- read %d bytes\n", n);
283 imap->data[n] = '\0';
284 if(imap->debug)
285 fprint(2, "<- %s\n", imap->data);
286 imap->data += n;
287 imap->size -= n;
288 p = Brdline(&imap->bin, '\n');
289 if(imap->debug)
290 fprint(2, "<- ignoring %.*s\n",
291 Blinelen(&imap->bin), p);
292 }else if((q = strchr(p, '"')) && (r = strchr(q+1, '"'))){
293 *r = '\0';
294 q++;
295 n = r-q;
296 if(imap->data == nil || n >= imap->size)
297 imapgrow(imap, n);
298 memmove(imap->data, q, n);
299 imap->data[n] = '\0';
300 imap->data += n;
301 imap->size -= n;
302 }else
303 return "confused about FETCH response";
304 break;
305 }
306
307 // * 1 FETCH (UID 1)
308 // * 2 FETCH (UID 6)
309 if(q = strstr(p, "UID")){
310 if(imap->nuid < imap->muid)
311 imap->uid[imap->nuid++] = ((vlong)imap->validity<<32)|strtoul(q+3, nil, 10);
312 break;
313 }
314 }
315
316 if(imap->tag == 0)
317 return line;
318 break;
319
320 case '9': // response to our message
321 op = p;
322 if(p[1]=='X' && strtoul(p+2, &p, 10)==imap->tag){
323 while(*p==' ')
324 p++;
325 imap->tag++;
326 return p;
327 }
328 fprint(2, "expected %lud; got %s\n", imap->tag, op);
329 break;
330
331 default:
332 if(imap->debug || *p)
333 fprint(2, "unexpected line: %s\n", p);
334 }
335 }
336 snprint(error, sizeof error, "i/o error: %r\n");
337 return error;
338 }
339
340 static int
isokay(char * resp)341 isokay(char *resp)
342 {
343 return strncmp(resp, "OK", 2)==0;
344 }
345
346 //
347 // log in to IMAP4 server, select mailbox, no SSL at the moment
348 //
349 static char*
imap4login(Imap * imap)350 imap4login(Imap *imap)
351 {
352 char *s;
353 UserPasswd *up;
354
355 imap->tag = 0;
356 s = imap4resp(imap);
357 if(!isokay(s))
358 return "error in initial IMAP handshake";
359
360 if(imap->user != nil)
361 up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q user=%q", imap->host, imap->user);
362 else
363 up = auth_getuserpasswd(auth_getkey, "proto=pass service=imap server=%q", imap->host);
364 if(up == nil)
365 return "cannot find IMAP password";
366
367 imap->tag = 1;
368 imap4cmd(imap, "LOGIN %Z %Z", up->user, up->passwd);
369 free(up);
370 if(!isokay(s = imap4resp(imap)))
371 return s;
372
373 imap4cmd(imap, "SELECT %Z", imap->mbox);
374 if(!isokay(s = imap4resp(imap)))
375 return s;
376
377 return nil;
378 }
379
380 static char*
imaperrstr(char * host,char * port)381 imaperrstr(char *host, char *port)
382 {
383 /*
384 * make mess big enough to hold a TLS certificate fingerprint
385 * plus quite a bit of slop.
386 */
387 static char mess[3 * Errlen];
388 char err[Errlen];
389
390 err[0] = '\0';
391 errstr(err, sizeof(err));
392 snprint(mess, sizeof(mess), "%s/%s:%s", host, port, err);
393 return mess;
394 }
395
396 static int
starttls(Imap * imap,TLSconn * connp)397 starttls(Imap *imap, TLSconn *connp)
398 {
399 int sfd;
400 uchar digest[SHA1dlen];
401
402 fmtinstall('H', encodefmt);
403 memset(connp, 0, sizeof *connp);
404 sfd = tlsClient(imap->fd, connp);
405 if(sfd < 0) {
406 werrstr("tlsClient: %r");
407 return -1;
408 }
409 if(connp->cert==nil || connp->certlen <= 0) {
410 close(sfd);
411 werrstr("server did not provide TLS certificate");
412 return -1;
413 }
414 sha1(connp->cert, connp->certlen, digest, nil);
415 /*
416 * don't do this any more. our local it people are rotating their
417 * certificates faster than we can keep up.
418 */
419 if(0 && (!imap->thumb || !okThumbprint(digest, imap->thumb))){
420 close(sfd);
421 werrstr("server certificate %.*H not recognized",
422 SHA1dlen, digest);
423 return -1;
424 }
425 close(imap->fd);
426 imap->fd = sfd;
427 return sfd;
428 }
429
430 //
431 // dial and handshake with the imap server
432 //
433 static char*
imap4dial(Imap * imap)434 imap4dial(Imap *imap)
435 {
436 char *err, *port;
437 int sfd;
438 TLSconn conn;
439
440 if(imap->fd >= 0){
441 imap4cmd(imap, "noop");
442 if(isokay(imap4resp(imap)))
443 return nil;
444 close(imap->fd);
445 imap->fd = -1;
446 }
447
448 if(imap->mustssl)
449 port = "imaps";
450 else
451 port = "imap";
452
453 if((imap->fd = dial(netmkaddr(imap->host, "net", port), 0, 0, 0)) < 0)
454 return imaperrstr(imap->host, port);
455
456 if(imap->mustssl){
457 sfd = starttls(imap, &conn);
458 if (sfd < 0) {
459 free(conn.cert);
460 return imaperrstr(imap->host, port);
461 }
462 if(imap->debug){
463 char fn[128];
464 int fd;
465
466 snprint(fn, sizeof fn, "%s/ctl", conn.dir);
467 fd = open(fn, ORDWR);
468 if(fd < 0)
469 fprint(2, "opening ctl: %r\n");
470 if(fprint(fd, "debug") < 0)
471 fprint(2, "writing ctl: %r\n");
472 close(fd);
473 }
474 }
475 Binit(&imap->bin, imap->fd, OREAD);
476 Binit(&imap->bout, imap->fd, OWRITE);
477
478 if(err = imap4login(imap)) {
479 close(imap->fd);
480 return err;
481 }
482
483 return nil;
484 }
485
486 //
487 // close connection
488 //
489 static void
imap4hangup(Imap * imap)490 imap4hangup(Imap *imap)
491 {
492 imap4cmd(imap, "LOGOUT");
493 imap4resp(imap);
494 close(imap->fd);
495 }
496
497 //
498 // download a single message
499 //
500 static char*
imap4fetch(Mailbox * mb,Message * m)501 imap4fetch(Mailbox *mb, Message *m)
502 {
503 int i;
504 char *p, *s, sdigest[2*SHA1dlen+1];
505 Imap *imap;
506
507 imap = mb->aux;
508
509 imap->size = 0;
510
511 if(!isokay(s = imap4resp(imap)))
512 return s;
513
514 p = imap->base;
515 if(p == nil)
516 return "did not get message body";
517
518 removecr(p);
519 free(m->start);
520 m->start = p;
521 m->end = p+strlen(p);
522 m->bend = m->rbend = m->end;
523 m->header = m->start;
524
525 imap->base = nil;
526 imap->data = nil;
527
528 parse(m, 0, mb, 1);
529
530 // digest headers
531 sha1((uchar*)m->start, m->end - m->start, m->digest, nil);
532 for(i = 0; i < SHA1dlen; i++)
533 sprint(sdigest+2*i, "%2.2ux", m->digest[i]);
534 m->sdigest = s_copy(sdigest);
535
536 return nil;
537 }
538
539 //
540 // check for new messages on imap4 server
541 // download new messages, mark deleted messages
542 //
543 static char*
imap4read(Imap * imap,Mailbox * mb,int doplumb)544 imap4read(Imap *imap, Mailbox *mb, int doplumb)
545 {
546 char *s;
547 int i, ignore, nnew, t;
548 Message *m, *next, **l;
549
550 imap4cmd(imap, "STATUS %Z (MESSAGES UIDVALIDITY)", imap->mbox);
551 if(!isokay(s = imap4resp(imap)))
552 return s;
553
554 imap->nuid = 0;
555 imap->uid = erealloc(imap->uid, imap->nmsg*sizeof(imap->uid[0]));
556 imap->muid = imap->nmsg;
557
558 if(imap->nmsg > 0){
559 imap4cmd(imap, "UID FETCH 1:* UID");
560 if(!isokay(s = imap4resp(imap)))
561 return s;
562 }
563
564 l = &mb->root->part;
565 for(i=0; i<imap->nuid; i++){
566 ignore = 0;
567 while(*l != nil){
568 if((*l)->imapuid == imap->uid[i]){
569 ignore = 1;
570 l = &(*l)->next;
571 break;
572 }else{
573 // old mail, we don't have it anymore
574 if(doplumb)
575 mailplumb(mb, *l, 1);
576 (*l)->inmbox = 0;
577 (*l)->deleted = 1;
578 l = &(*l)->next;
579 }
580 }
581 if(ignore)
582 continue;
583
584 // new message
585 m = newmessage(mb->root);
586 m->mallocd = 1;
587 m->inmbox = 1;
588 m->imapuid = imap->uid[i];
589
590 // add to chain, will download soon
591 *l = m;
592 l = &m->next;
593 }
594
595 // whatever is left at the end of the chain is gone
596 while(*l != nil){
597 if(doplumb)
598 mailplumb(mb, *l, 1);
599 (*l)->inmbox = 0;
600 (*l)->deleted = 1;
601 l = &(*l)->next;
602 }
603
604 // download new messages
605 t = imap->tag;
606 if(pipeline)
607 switch(rfork(RFPROC|RFMEM)){
608 case -1:
609 sysfatal("rfork: %r");
610 default:
611 break;
612 case 0:
613 for(m = mb->root->part; m != nil; m = m->next){
614 if(m->start != nil)
615 continue;
616 if(imap->debug)
617 fprint(2, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
618 t, (ulong)m->imapuid);
619 Bprint(&imap->bout, "9X%d UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
620 t++, (ulong)m->imapuid);
621 }
622 Bflush(&imap->bout);
623 _exits(nil);
624 }
625
626 nnew = 0;
627 for(m=mb->root->part; m!=nil; m=next){
628 next = m->next;
629 if(m->start != nil)
630 continue;
631
632 if(!pipeline){
633 Bprint(&imap->bout, "9X%lud UID FETCH %lud (UID RFC822.SIZE BODY[])\r\n",
634 (ulong)imap->tag, (ulong)m->imapuid);
635 Bflush(&imap->bout);
636 }
637
638 if(s = imap4fetch(mb, m)){
639 // message disappeared? unchain
640 fprint(2, "download %lud: %s\n", (ulong)m->imapuid, s);
641 delmessage(mb, m);
642 mb->root->subname--;
643 continue;
644 }
645 nnew++;
646 if(doplumb)
647 mailplumb(mb, m, 0);
648 }
649 if(pipeline)
650 waitpid();
651
652 if(nnew || mb->vers == 0){
653 mb->vers++;
654 henter(PATH(0, Qtop), mb->name,
655 (Qid){PATH(mb->id, Qmbox), mb->vers, QTDIR}, nil, mb);
656 }
657 return nil;
658 }
659
660 //
661 // sync mailbox
662 //
663 static void
imap4purge(Imap * imap,Mailbox * mb)664 imap4purge(Imap *imap, Mailbox *mb)
665 {
666 int ndel;
667 Message *m, *next;
668
669 ndel = 0;
670 for(m=mb->root->part; m!=nil; m=next){
671 next = m->next;
672 if(m->deleted && m->refs==0){
673 if(m->inmbox && (ulong)(m->imapuid>>32)==imap->validity){
674 imap4cmd(imap, "UID STORE %lud +FLAGS (\\Deleted)", (ulong)m->imapuid);
675 if(isokay(imap4resp(imap))){
676 ndel++;
677 delmessage(mb, m);
678 }
679 }else
680 delmessage(mb, m);
681 }
682 }
683
684 if(ndel){
685 imap4cmd(imap, "EXPUNGE");
686 imap4resp(imap);
687 }
688 }
689
690 //
691 // connect to imap4 server, sync mailbox
692 //
693 static char*
imap4sync(Mailbox * mb,int doplumb)694 imap4sync(Mailbox *mb, int doplumb)
695 {
696 char *err;
697 Imap *imap;
698
699 imap = mb->aux;
700
701 if(err = imap4dial(imap)){
702 mb->waketime = time(0) + imap->refreshtime;
703 return err;
704 }
705
706 if((err = imap4read(imap, mb, doplumb)) == nil){
707 imap4purge(imap, mb);
708 mb->d->atime = mb->d->mtime = time(0);
709 }
710 /*
711 * don't hang up; leave connection open for next time.
712 */
713 // imap4hangup(imap);
714 mb->waketime = time(0) + imap->refreshtime;
715 return err;
716 }
717
718 static char Eimap4ctl[] = "bad imap4 control message";
719
720 static char*
imap4ctl(Mailbox * mb,int argc,char ** argv)721 imap4ctl(Mailbox *mb, int argc, char **argv)
722 {
723 int n;
724 Imap *imap;
725
726 imap = mb->aux;
727 if(argc < 1)
728 return Eimap4ctl;
729
730 if(argc==1 && strcmp(argv[0], "debug")==0){
731 imap->debug = 1;
732 return nil;
733 }
734
735 if(argc==1 && strcmp(argv[0], "nodebug")==0){
736 imap->debug = 0;
737 return nil;
738 }
739
740 if(argc==1 && strcmp(argv[0], "thumbprint")==0){
741 if(imap->thumb)
742 freeThumbprints(imap->thumb);
743 imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
744 }
745 if(strcmp(argv[0], "refresh")==0){
746 if(argc==1){
747 imap->refreshtime = 60;
748 return nil;
749 }
750 if(argc==2){
751 n = atoi(argv[1]);
752 if(n < 15)
753 return Eimap4ctl;
754 imap->refreshtime = n;
755 return nil;
756 }
757 }
758
759 return Eimap4ctl;
760 }
761
762 //
763 // free extra memory associated with mb
764 //
765 static void
imap4close(Mailbox * mb)766 imap4close(Mailbox *mb)
767 {
768 Imap *imap;
769
770 imap = mb->aux;
771 free(imap->freep);
772 free(imap->base);
773 free(imap->uid);
774 if(imap->fd >= 0)
775 close(imap->fd);
776 free(imap);
777 }
778
779 //
780 // open mailboxes of the form /imap/host/user
781 //
782 char*
imap4mbox(Mailbox * mb,char * path)783 imap4mbox(Mailbox *mb, char *path)
784 {
785 char *f[10];
786 int mustssl, nf;
787 Imap *imap;
788
789 quotefmtinstall();
790 fmtinstall('Z', doublequote);
791 if(strncmp(path, "/imap/", 6) != 0 && strncmp(path, "/imaps/", 7) != 0)
792 return Enotme;
793 mustssl = (strncmp(path, "/imaps/", 7) == 0);
794
795 path = strdup(path);
796 if(path == nil)
797 return "out of memory";
798
799 nf = getfields(path, f, 5, 0, "/");
800 if(nf < 3){
801 free(path);
802 return "bad imap path syntax /imap[s]/system[/user[/mailbox]]";
803 }
804
805 imap = emalloc(sizeof(*imap));
806 imap->fd = -1;
807 imap->debug = debug;
808 imap->freep = path;
809 imap->mustssl = mustssl;
810 imap->host = f[2];
811 if(nf < 4)
812 imap->user = nil;
813 else
814 imap->user = f[3];
815 if(nf < 5)
816 imap->mbox = "Inbox";
817 else
818 imap->mbox = f[4];
819 imap->thumb = initThumbprints("/sys/lib/tls/mail", "/sys/lib/tls/mail.exclude");
820
821 mb->aux = imap;
822 mb->sync = imap4sync;
823 mb->close = imap4close;
824 mb->ctl = imap4ctl;
825 mb->d = emalloc(sizeof(*mb->d));
826 //mb->fetch = imap4fetch;
827
828 return nil;
829 }
830
831 //
832 // Formatter for %"
833 // Use double quotes to protect white space, frogs, \ and "
834 //
835 enum
836 {
837 Qok = 0,
838 Qquote,
839 Qbackslash,
840 };
841
842 static int
needtoquote(Rune r)843 needtoquote(Rune r)
844 {
845 if(r >= Runeself)
846 return Qquote;
847 if(r <= ' ')
848 return Qquote;
849 if(r=='\\' || r=='"')
850 return Qbackslash;
851 return Qok;
852 }
853
854 int
doublequote(Fmt * f)855 doublequote(Fmt *f)
856 {
857 char *s, *t;
858 int w, quotes;
859 Rune r;
860
861 s = va_arg(f->args, char*);
862 if(s == nil || *s == '\0')
863 return fmtstrcpy(f, "\"\"");
864
865 quotes = 0;
866 for(t=s; *t; t+=w){
867 w = chartorune(&r, t);
868 quotes |= needtoquote(r);
869 }
870 if(quotes == 0)
871 return fmtstrcpy(f, s);
872
873 fmtrune(f, '"');
874 for(t=s; *t; t+=w){
875 w = chartorune(&r, t);
876 if(needtoquote(r) == Qbackslash)
877 fmtrune(f, '\\');
878 fmtrune(f, r);
879 }
880 return fmtrune(f, '"');
881 }
882