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