1package main
2
3import (
4 "encoding/json"
5 "flag"
6 "fmt"
7 "io"
8 "log"
9 "os"
10 "strconv"
11 "strings"
12 "time"
13
14 "github.com/mjl-/mox/queue"
15)
16
17func xctlwriteJSON(ctl *ctl, v any) {
18 fbuf, err := json.Marshal(v)
19 xcheckf(err, "marshal as json to ctl")
20 ctl.xwrite(string(fbuf))
21}
22
23func cmdQueueHoldrulesList(c *cmd) {
24 c.help = `List hold rules for the delivery queue.
25
26Messages submitted to the queue that match a hold rule will be marked as on hold
27and not scheduled for delivery.
28`
29 if len(c.Parse()) != 0 {
30 c.Usage()
31 }
32 mustLoadConfig()
33 ctlcmdQueueHoldrulesList(xctl())
34}
35
36func ctlcmdQueueHoldrulesList(ctl *ctl) {
37 ctl.xwrite("queueholdruleslist")
38 ctl.xreadok()
39 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
40 log.Fatalf("%s", err)
41 }
42}
43
44func cmdQueueHoldrulesAdd(c *cmd) {
45 c.params = "[ruleflags]"
46 c.help = `Add hold rule for the delivery queue.
47
48Add a hold rule to mark matching newly submitted messages as on hold. Set the
49matching rules with the flags. Don't specify any flags to match all submitted
50messages.
51`
52 var account, senderDomain, recipientDomain string
53 c.flag.StringVar(&account, "account", "", "account submitting the message")
54 c.flag.StringVar(&senderDomain, "senderdom", "", "sender domain")
55 c.flag.StringVar(&recipientDomain, "recipientdom", "", "recipient domain")
56 if len(c.Parse()) != 0 {
57 c.Usage()
58 }
59 mustLoadConfig()
60 ctlcmdQueueHoldrulesAdd(xctl(), account, senderDomain, recipientDomain)
61}
62
63func ctlcmdQueueHoldrulesAdd(ctl *ctl, account, senderDomain, recipientDomain string) {
64 ctl.xwrite("queueholdrulesadd")
65 ctl.xwrite(account)
66 ctl.xwrite(senderDomain)
67 ctl.xwrite(recipientDomain)
68 ctl.xreadok()
69}
70
71func cmdQueueHoldrulesRemove(c *cmd) {
72 c.params = "ruleid"
73 c.help = `Remove hold rule for the delivery queue.
74
75Remove a hold rule by its id.
76`
77 args := c.Parse()
78 if len(args) != 1 {
79 c.Usage()
80 }
81 id, err := strconv.ParseInt(args[0], 10, 64)
82 xcheckf(err, "parsing id")
83 mustLoadConfig()
84 ctlcmdQueueHoldrulesRemove(xctl(), id)
85}
86
87func ctlcmdQueueHoldrulesRemove(ctl *ctl, id int64) {
88 ctl.xwrite("queueholdrulesremove")
89 ctl.xwrite(fmt.Sprintf("%d", id))
90 ctl.xreadok()
91}
92
93// flagFilterSort is used by many of the queue commands to accept flags for
94// filtering the messages the operation applies to.
95func flagFilterSort(fs *flag.FlagSet, f *queue.Filter, s *queue.Sort) {
96 fs.Func("ids", "comma-separated list of message IDs", func(v string) error {
97 for _, s := range strings.Split(v, ",") {
98 id, err := strconv.ParseInt(s, 10, 64)
99 if err != nil {
100 return err
101 }
102 f.IDs = append(f.IDs, id)
103 }
104 return nil
105 })
106 fs.IntVar(&f.Max, "n", 0, "number of messages to return")
107 fs.StringVar(&f.Account, "account", "", "account that queued the message")
108 fs.StringVar(&f.From, "from", "", `from address of message, use "@example.com" to match all messages for a domain`)
109 fs.StringVar(&f.To, "to", "", `recipient address of message, use "@example.com" to match all messages for a domain`)
110 fs.StringVar(&f.Submitted, "submitted", "", `filter by time of submission relative to now, value must start with "<" (before now) or ">" (after now)`)
111 fs.StringVar(&f.NextAttempt, "nextattempt", "", `filter by time of next delivery attempt relative to now, value must start with "<" (before now) or ">" (after now)`)
112 fs.Func("transport", "transport to use for messages, empty string sets the default behaviour", func(v string) error {
113 f.Transport = &v
114 return nil
115 })
116 fs.Func("hold", "true or false, whether to match only messages that are (not) on hold", func(v string) error {
117 var hold bool
118 if v == "true" {
119 hold = true
120 } else if v == "false" {
121 hold = false
122 } else {
123 return fmt.Errorf("bad value %q", v)
124 }
125 f.Hold = &hold
126 return nil
127 })
128 if s != nil {
129 fs.Func("sort", `field to sort by, "nextattempt" (default) or "queued"`, func(v string) error {
130 switch v {
131 case "nextattempt":
132 s.Field = "NextAttempt"
133 case "queued":
134 s.Field = "Queued"
135 default:
136 return fmt.Errorf("unknown value %q", v)
137 }
138 return nil
139 })
140 fs.BoolVar(&s.Asc, "asc", false, "sort ascending instead of descending (default)")
141 }
142}
143
144// flagRetiredFilterSort has filters for retired messages.
145func flagRetiredFilterSort(fs *flag.FlagSet, f *queue.RetiredFilter, s *queue.RetiredSort) {
146 fs.Func("ids", "comma-separated list of retired message IDs", func(v string) error {
147 for _, s := range strings.Split(v, ",") {
148 id, err := strconv.ParseInt(s, 10, 64)
149 if err != nil {
150 return err
151 }
152 f.IDs = append(f.IDs, id)
153 }
154 return nil
155 })
156 fs.IntVar(&f.Max, "n", 0, "number of messages to return")
157 fs.StringVar(&f.Account, "account", "", "account that queued the message")
158 fs.StringVar(&f.From, "from", "", `from address of message, use "@example.com" to match all messages for a domain`)
159 fs.StringVar(&f.To, "to", "", `recipient address of message, use "@example.com" to match all messages for a domain`)
160 fs.StringVar(&f.Submitted, "submitted", "", `filter by time of submission relative to now, value must start with "<" (before now) or ">" (after now)`)
161 fs.StringVar(&f.LastActivity, "lastactivity", "", `filter by time of last activity relative to now, value must start with "<" (before now) or ">" (after now)`)
162 fs.Func("transport", "transport to use for messages, empty string sets the default behaviour", func(v string) error {
163 f.Transport = &v
164 return nil
165 })
166 fs.Func("result", `"success" or "failure" as result of delivery`, func(v string) error {
167 switch v {
168 case "success":
169 t := true
170 f.Success = &t
171 case "failure":
172 t := false
173 f.Success = &t
174 default:
175 return fmt.Errorf("bad argument %q, need success or failure", v)
176 }
177 return nil
178 })
179 if s != nil {
180 fs.Func("sort", `field to sort by, "lastactivity" (default) or "queued"`, func(v string) error {
181 switch v {
182 case "lastactivity":
183 s.Field = "LastActivity"
184 case "queued":
185 s.Field = "Queued"
186 default:
187 return fmt.Errorf("unknown value %q", v)
188 }
189 return nil
190 })
191 fs.BoolVar(&s.Asc, "asc", false, "sort ascending instead of descending (default)")
192 }
193}
194
195func cmdQueueList(c *cmd) {
196 c.params = "[filtersortflags]"
197 c.help = `List matching messages in the delivery queue.
198
199Prints the message with its ID, last and next delivery attempts, last error.
200`
201 var f queue.Filter
202 var s queue.Sort
203 flagFilterSort(c.flag, &f, &s)
204 if len(c.Parse()) != 0 {
205 c.Usage()
206 }
207 mustLoadConfig()
208 ctlcmdQueueList(xctl(), f, s)
209}
210
211func ctlcmdQueueList(ctl *ctl, f queue.Filter, s queue.Sort) {
212 ctl.xwrite("queuelist")
213 xctlwriteJSON(ctl, f)
214 xctlwriteJSON(ctl, s)
215 ctl.xreadok()
216 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
217 log.Fatalf("%s", err)
218 }
219}
220
221func cmdQueueHold(c *cmd) {
222 c.params = "[filterflags]"
223 c.help = `Mark matching messages on hold.
224
225Messages that are on hold are not delivered until marked as off hold again, or
226otherwise handled by the admin.
227`
228 var f queue.Filter
229 flagFilterSort(c.flag, &f, nil)
230 if len(c.Parse()) != 0 {
231 c.Usage()
232 }
233 mustLoadConfig()
234 ctlcmdQueueHoldSet(xctl(), f, true)
235}
236
237func cmdQueueUnhold(c *cmd) {
238 c.params = "[filterflags]"
239 c.help = `Mark matching messages off hold.
240
241Once off hold, messages can be delivered according to their current next
242delivery attempt. See the "queue schedule" command.
243`
244 var f queue.Filter
245 flagFilterSort(c.flag, &f, nil)
246 if len(c.Parse()) != 0 {
247 c.Usage()
248 }
249 mustLoadConfig()
250 ctlcmdQueueHoldSet(xctl(), f, false)
251}
252
253func ctlcmdQueueHoldSet(ctl *ctl, f queue.Filter, hold bool) {
254 ctl.xwrite("queueholdset")
255 xctlwriteJSON(ctl, f)
256 if hold {
257 ctl.xwrite("true")
258 } else {
259 ctl.xwrite("false")
260 }
261 line := ctl.xread()
262 if line == "ok" {
263 fmt.Printf("%s messages changed\n", ctl.xread())
264 } else {
265 log.Fatalf("%s", line)
266 }
267}
268
269func cmdQueueSchedule(c *cmd) {
270 c.params = "[filterflags] [-now] duration"
271 c.help = `Change next delivery attempt for matching messages.
272
273The next delivery attempt is adjusted by the duration parameter. If the -now
274flag is set, the new delivery attempt is set to the duration added to the
275current time, instead of added to the current scheduled time.
276
277Schedule immediate delivery with "mox queue schedule -now 0".
278`
279 var fromNow bool
280 c.flag.BoolVar(&fromNow, "now", false, "schedule for duration relative to current time instead of relative to current next delivery attempt for messages")
281 var f queue.Filter
282 flagFilterSort(c.flag, &f, nil)
283 args := c.Parse()
284 if len(args) != 1 {
285 c.Usage()
286 }
287 d, err := time.ParseDuration(args[0])
288 xcheckf(err, "parsing duration %q", args[0])
289 mustLoadConfig()
290 ctlcmdQueueSchedule(xctl(), f, fromNow, d)
291}
292
293func ctlcmdQueueSchedule(ctl *ctl, f queue.Filter, fromNow bool, d time.Duration) {
294 ctl.xwrite("queueschedule")
295 xctlwriteJSON(ctl, f)
296 if fromNow {
297 ctl.xwrite("yes")
298 } else {
299 ctl.xwrite("")
300 }
301 ctl.xwrite(d.String())
302 line := ctl.xread()
303 if line == "ok" {
304 fmt.Printf("%s message(s) rescheduled\n", ctl.xread())
305 } else {
306 log.Fatalf("%s", line)
307 }
308}
309
310func cmdQueueTransport(c *cmd) {
311 c.params = "[filterflags] transport"
312 c.help = `Set transport for matching messages.
313
314By default, the routing rules determine how a message is delivered. The default
315and common case is direct delivery with SMTP. Messages can get a previously
316configured transport assigned to use for delivery, e.g. using submission to
317another mail server or with connections over a SOCKS proxy.
318`
319 var f queue.Filter
320 flagFilterSort(c.flag, &f, nil)
321 args := c.Parse()
322 if len(args) != 1 {
323 c.Usage()
324 }
325 mustLoadConfig()
326 ctlcmdQueueTransport(xctl(), f, args[0])
327}
328
329func ctlcmdQueueTransport(ctl *ctl, f queue.Filter, transport string) {
330 ctl.xwrite("queuetransport")
331 xctlwriteJSON(ctl, f)
332 ctl.xwrite(transport)
333 line := ctl.xread()
334 if line == "ok" {
335 fmt.Printf("%s message(s) changed\n", ctl.xread())
336 } else {
337 log.Fatalf("%s", line)
338 }
339}
340
341func cmdQueueRequireTLS(c *cmd) {
342 c.params = "[filterflags] {yes | no | default}"
343 c.help = `Set TLS requirements for delivery of matching messages.
344
345Value "yes" is handled as if the RequireTLS extension was specified during
346submission.
347
348Value "no" is handled as if the message has a header "TLS-Required: No". This
349header is not added by the queue. If messages without this header are relayed
350through other mail servers they will apply their own default TLS policy.
351
352Value "default" is the default behaviour, currently for unverified opportunistic
353TLS.
354`
355 var f queue.Filter
356 flagFilterSort(c.flag, &f, nil)
357 args := c.Parse()
358 if len(args) != 1 {
359 c.Usage()
360 }
361 var tlsreq *bool
362 switch args[0] {
363 case "yes":
364 v := true
365 tlsreq = &v
366 case "no":
367 v := false
368 tlsreq = &v
369 case "default":
370 default:
371 c.Usage()
372 }
373 mustLoadConfig()
374 ctlcmdQueueRequireTLS(xctl(), f, tlsreq)
375}
376
377func ctlcmdQueueRequireTLS(ctl *ctl, f queue.Filter, tlsreq *bool) {
378 ctl.xwrite("queuerequiretls")
379 xctlwriteJSON(ctl, f)
380 var req string
381 if tlsreq == nil {
382 req = ""
383 } else if *tlsreq {
384 req = "true"
385 } else {
386 req = "false"
387 }
388 ctl.xwrite(req)
389 line := ctl.xread()
390 if line == "ok" {
391 fmt.Printf("%s message(s) changed\n", ctl.xread())
392 } else {
393 log.Fatalf("%s", line)
394 }
395}
396
397func cmdQueueFail(c *cmd) {
398 c.params = "[filterflags]"
399 c.help = `Fail delivery of matching messages, delivering DSNs.
400
401Failing a message is handled similar to how delivery is given up after all
402delivery attempts failed. The DSN (delivery status notification) message
403contains a line saying the message was canceled by the admin.
404`
405 var f queue.Filter
406 flagFilterSort(c.flag, &f, nil)
407 if len(c.Parse()) != 0 {
408 c.Usage()
409 }
410 mustLoadConfig()
411 ctlcmdQueueFail(xctl(), f)
412}
413
414func ctlcmdQueueFail(ctl *ctl, f queue.Filter) {
415 ctl.xwrite("queuefail")
416 xctlwriteJSON(ctl, f)
417 line := ctl.xread()
418 if line == "ok" {
419 fmt.Printf("%s message(s) marked as failed\n", ctl.xread())
420 } else {
421 log.Fatalf("%s", line)
422 }
423}
424
425func cmdQueueDrop(c *cmd) {
426 c.params = "[filterflags]"
427 c.help = `Remove matching messages from the queue.
428
429Dangerous operation, this completely removes the message. If you want to store
430the message, use "queue dump" before removing.
431`
432 var f queue.Filter
433 flagFilterSort(c.flag, &f, nil)
434 if len(c.Parse()) != 0 {
435 c.Usage()
436 }
437 mustLoadConfig()
438 ctlcmdQueueDrop(xctl(), f)
439}
440
441func ctlcmdQueueDrop(ctl *ctl, f queue.Filter) {
442 ctl.xwrite("queuedrop")
443 xctlwriteJSON(ctl, f)
444 line := ctl.xread()
445 if line == "ok" {
446 fmt.Printf("%s message(s) dropped\n", ctl.xread())
447 } else {
448 log.Fatalf("%s", line)
449 }
450}
451
452func cmdQueueDump(c *cmd) {
453 c.params = "id"
454 c.help = `Dump a message from the queue.
455
456The message is printed to stdout and is in standard internet mail format.
457`
458 args := c.Parse()
459 if len(args) != 1 {
460 c.Usage()
461 }
462 mustLoadConfig()
463 ctlcmdQueueDump(xctl(), args[0])
464}
465
466func ctlcmdQueueDump(ctl *ctl, id string) {
467 ctl.xwrite("queuedump")
468 ctl.xwrite(id)
469 ctl.xreadok()
470 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
471 log.Fatalf("%s", err)
472 }
473}
474
475func cmdQueueSuppressList(c *cmd) {
476 c.params = "[-account account]"
477 c.help = `Print addresses in suppression list.`
478 var account string
479 c.flag.StringVar(&account, "account", "", "only show suppression list for this account")
480 args := c.Parse()
481 if len(args) != 0 {
482 c.Usage()
483 }
484 mustLoadConfig()
485 ctlcmdQueueSuppressList(xctl(), account)
486}
487
488func ctlcmdQueueSuppressList(ctl *ctl, account string) {
489 ctl.xwrite("queuesuppresslist")
490 ctl.xwrite(account)
491 ctl.xreadok()
492 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
493 log.Fatalf("%s", err)
494 }
495}
496
497func cmdQueueSuppressAdd(c *cmd) {
498 c.params = "account address"
499 c.help = `Add address to suppression list for account.`
500 args := c.Parse()
501 if len(args) != 2 {
502 c.Usage()
503 }
504 mustLoadConfig()
505 ctlcmdQueueSuppressAdd(xctl(), args[0], args[1])
506}
507
508func ctlcmdQueueSuppressAdd(ctl *ctl, account, address string) {
509 ctl.xwrite("queuesuppressadd")
510 ctl.xwrite(account)
511 ctl.xwrite(address)
512 ctl.xreadok()
513}
514
515func cmdQueueSuppressRemove(c *cmd) {
516 c.params = "account address"
517 c.help = `Remove address from suppression list for account.`
518 args := c.Parse()
519 if len(args) != 2 {
520 c.Usage()
521 }
522 mustLoadConfig()
523 ctlcmdQueueSuppressRemove(xctl(), args[0], args[1])
524}
525
526func ctlcmdQueueSuppressRemove(ctl *ctl, account, address string) {
527 ctl.xwrite("queuesuppressremove")
528 ctl.xwrite(account)
529 ctl.xwrite(address)
530 ctl.xreadok()
531}
532
533func cmdQueueSuppressLookup(c *cmd) {
534 c.params = "[-account account] address"
535 c.help = `Check if address is present in suppression list, for any or specific account.`
536 var account string
537 c.flag.StringVar(&account, "account", "", "only check address in specified account")
538 args := c.Parse()
539 if len(args) != 1 {
540 c.Usage()
541 }
542 mustLoadConfig()
543 ctlcmdQueueSuppressLookup(xctl(), account, args[0])
544}
545
546func ctlcmdQueueSuppressLookup(ctl *ctl, account, address string) {
547 ctl.xwrite("queuesuppresslookup")
548 ctl.xwrite(account)
549 ctl.xwrite(address)
550 ctl.xreadok()
551 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
552 log.Fatalf("%s", err)
553 }
554}
555
556func cmdQueueRetiredList(c *cmd) {
557 c.params = "[filtersortflags]"
558 c.help = `List matching messages in the retired queue.
559
560Prints messages with their ID and results.
561`
562 var f queue.RetiredFilter
563 var s queue.RetiredSort
564 flagRetiredFilterSort(c.flag, &f, &s)
565 if len(c.Parse()) != 0 {
566 c.Usage()
567 }
568 mustLoadConfig()
569 ctlcmdQueueRetiredList(xctl(), f, s)
570}
571
572func ctlcmdQueueRetiredList(ctl *ctl, f queue.RetiredFilter, s queue.RetiredSort) {
573 ctl.xwrite("queueretiredlist")
574 xctlwriteJSON(ctl, f)
575 xctlwriteJSON(ctl, s)
576 ctl.xreadok()
577 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
578 log.Fatalf("%s", err)
579 }
580}
581
582func cmdQueueRetiredPrint(c *cmd) {
583 c.params = "id"
584 c.help = `Print a message from the retired queue.
585
586Prints a JSON representation of the information from the retired queue.
587`
588 args := c.Parse()
589 if len(args) != 1 {
590 c.Usage()
591 }
592 mustLoadConfig()
593 ctlcmdQueueRetiredPrint(xctl(), args[0])
594}
595
596func ctlcmdQueueRetiredPrint(ctl *ctl, id string) {
597 ctl.xwrite("queueretiredprint")
598 ctl.xwrite(id)
599 ctl.xreadok()
600 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
601 log.Fatalf("%s", err)
602 }
603}
604
605// note: outgoing hook events are in queue/hooks.go, mox-/config.go, queue.go and webapi/gendoc.sh. keep in sync.
606
607// flagHookFilterSort is used by many of the queue commands to accept flags for
608// filtering the webhooks the operation applies to.
609func flagHookFilterSort(fs *flag.FlagSet, f *queue.HookFilter, s *queue.HookSort) {
610 fs.Func("ids", "comma-separated list of webhook IDs", func(v string) error {
611 for _, s := range strings.Split(v, ",") {
612 id, err := strconv.ParseInt(s, 10, 64)
613 if err != nil {
614 return err
615 }
616 f.IDs = append(f.IDs, id)
617 }
618 return nil
619 })
620 fs.IntVar(&f.Max, "n", 0, "number of webhooks to return")
621 fs.StringVar(&f.Account, "account", "", "account that queued the message/webhook")
622 fs.StringVar(&f.Submitted, "submitted", "", `filter by time of submission relative to now, value must start with "<" (before now) or ">" (after now)`)
623 fs.StringVar(&f.NextAttempt, "nextattempt", "", `filter by time of next delivery attempt relative to now, value must start with "<" (before now) or ">" (after now)`)
624 fs.Func("event", `event this webhook is about: incoming, delivered, suppressed, delayed, failed, relayed, expanded, canceled, unrecognized`, func(v string) error {
625 switch v {
626 case "incoming", "delivered", "suppressed", "delayed", "failed", "relayed", "expanded", "canceled", "unrecognized":
627 f.Event = v
628 default:
629 return fmt.Errorf("invalid parameter %q", v)
630 }
631 return nil
632 })
633 if s != nil {
634 fs.Func("sort", `field to sort by, "nextattempt" (default) or "queued"`, func(v string) error {
635 switch v {
636 case "nextattempt":
637 s.Field = "NextAttempt"
638 case "queued":
639 s.Field = "Queued"
640 default:
641 return fmt.Errorf("unknown value %q", v)
642 }
643 return nil
644 })
645 fs.BoolVar(&s.Asc, "asc", false, "sort ascending instead of descending (default)")
646 }
647}
648
649// flagHookRetiredFilterSort is used by many of the queue commands to accept flags
650// for filtering the webhooks the operation applies to.
651func flagHookRetiredFilterSort(fs *flag.FlagSet, f *queue.HookRetiredFilter, s *queue.HookRetiredSort) {
652 fs.Func("ids", "comma-separated list of retired webhook IDs", func(v string) error {
653 for _, s := range strings.Split(v, ",") {
654 id, err := strconv.ParseInt(s, 10, 64)
655 if err != nil {
656 return err
657 }
658 f.IDs = append(f.IDs, id)
659 }
660 return nil
661 })
662 fs.IntVar(&f.Max, "n", 0, "number of webhooks to return")
663 fs.StringVar(&f.Account, "account", "", "account that queued the message/webhook")
664 fs.StringVar(&f.Submitted, "submitted", "", `filter by time of submission relative to now, value must start with "<" (before now) or ">" (after now)`)
665 fs.StringVar(&f.LastActivity, "lastactivity", "", `filter by time of last activity relative to now, value must start with "<" (before now) or ">" (after now)`)
666 fs.Func("event", `event this webhook is about: incoming, delivered, suppressed, delayed, failed, relayed, expanded, canceled, unrecognized`, func(v string) error {
667 switch v {
668 case "incoming", "delivered", "suppressed", "delayed", "failed", "relayed", "expanded", "canceled", "unrecognized":
669 f.Event = v
670 default:
671 return fmt.Errorf("invalid parameter %q", v)
672 }
673 return nil
674 })
675 if s != nil {
676 fs.Func("sort", `field to sort by, "lastactivity" (default) or "queued"`, func(v string) error {
677 switch v {
678 case "lastactivity":
679 s.Field = "LastActivity"
680 case "queued":
681 s.Field = "Queued"
682 default:
683 return fmt.Errorf("unknown value %q", v)
684 }
685 return nil
686 })
687 fs.BoolVar(&s.Asc, "asc", false, "sort ascending instead of descending (default)")
688 }
689}
690
691func cmdQueueHookList(c *cmd) {
692 c.params = "[filtersortflags]"
693 c.help = `List matching webhooks in the queue.
694
695Prints list of webhooks, their IDs and basic information.
696`
697 var f queue.HookFilter
698 var s queue.HookSort
699 flagHookFilterSort(c.flag, &f, &s)
700 if len(c.Parse()) != 0 {
701 c.Usage()
702 }
703 mustLoadConfig()
704 ctlcmdQueueHookList(xctl(), f, s)
705}
706
707func ctlcmdQueueHookList(ctl *ctl, f queue.HookFilter, s queue.HookSort) {
708 ctl.xwrite("queuehooklist")
709 xctlwriteJSON(ctl, f)
710 xctlwriteJSON(ctl, s)
711 ctl.xreadok()
712 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
713 log.Fatalf("%s", err)
714 }
715}
716
717func cmdQueueHookSchedule(c *cmd) {
718 c.params = "[filterflags] duration"
719 c.help = `Change next delivery attempt for matching webhooks.
720
721The next delivery attempt is adjusted by the duration parameter. If the -now
722flag is set, the new delivery attempt is set to the duration added to the
723current time, instead of added to the current scheduled time.
724
725Schedule immediate delivery with "mox queue schedule -now 0".
726`
727 var fromNow bool
728 c.flag.BoolVar(&fromNow, "now", false, "schedule for duration relative to current time instead of relative to current next delivery attempt for webhooks")
729 var f queue.HookFilter
730 flagHookFilterSort(c.flag, &f, nil)
731 args := c.Parse()
732 if len(args) != 1 {
733 c.Usage()
734 }
735 d, err := time.ParseDuration(args[0])
736 xcheckf(err, "parsing duration %q", args[0])
737 mustLoadConfig()
738 ctlcmdQueueHookSchedule(xctl(), f, fromNow, d)
739}
740
741func ctlcmdQueueHookSchedule(ctl *ctl, f queue.HookFilter, fromNow bool, d time.Duration) {
742 ctl.xwrite("queuehookschedule")
743 xctlwriteJSON(ctl, f)
744 if fromNow {
745 ctl.xwrite("yes")
746 } else {
747 ctl.xwrite("")
748 }
749 ctl.xwrite(d.String())
750 line := ctl.xread()
751 if line == "ok" {
752 fmt.Printf("%s webhook(s) rescheduled\n", ctl.xread())
753 } else {
754 log.Fatalf("%s", line)
755 }
756}
757
758func cmdQueueHookCancel(c *cmd) {
759 c.params = "[filterflags]"
760 c.help = `Fail delivery of matching webhooks.`
761 var f queue.HookFilter
762 flagHookFilterSort(c.flag, &f, nil)
763 if len(c.Parse()) != 0 {
764 c.Usage()
765 }
766 mustLoadConfig()
767 ctlcmdQueueHookCancel(xctl(), f)
768}
769
770func ctlcmdQueueHookCancel(ctl *ctl, f queue.HookFilter) {
771 ctl.xwrite("queuehookcancel")
772 xctlwriteJSON(ctl, f)
773 line := ctl.xread()
774 if line == "ok" {
775 fmt.Printf("%s webhook(s)s marked as canceled\n", ctl.xread())
776 } else {
777 log.Fatalf("%s", line)
778 }
779}
780
781func cmdQueueHookPrint(c *cmd) {
782 c.params = "id"
783 c.help = `Print details of a webhook from the queue.
784
785The webhook is printed to stdout as JSON.
786`
787 args := c.Parse()
788 if len(args) != 1 {
789 c.Usage()
790 }
791 mustLoadConfig()
792 ctlcmdQueueHookPrint(xctl(), args[0])
793}
794
795func ctlcmdQueueHookPrint(ctl *ctl, id string) {
796 ctl.xwrite("queuehookprint")
797 ctl.xwrite(id)
798 ctl.xreadok()
799 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
800 log.Fatalf("%s", err)
801 }
802}
803
804func cmdQueueHookRetiredList(c *cmd) {
805 c.params = "[filtersortflags]"
806 c.help = `List matching webhooks in the retired queue.
807
808Prints list of retired webhooks, their IDs and basic information.
809`
810 var f queue.HookRetiredFilter
811 var s queue.HookRetiredSort
812 flagHookRetiredFilterSort(c.flag, &f, &s)
813 if len(c.Parse()) != 0 {
814 c.Usage()
815 }
816 mustLoadConfig()
817 ctlcmdQueueHookRetiredList(xctl(), f, s)
818}
819
820func ctlcmdQueueHookRetiredList(ctl *ctl, f queue.HookRetiredFilter, s queue.HookRetiredSort) {
821 ctl.xwrite("queuehookretiredlist")
822 xctlwriteJSON(ctl, f)
823 xctlwriteJSON(ctl, s)
824 ctl.xreadok()
825 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
826 log.Fatalf("%s", err)
827 }
828}
829
830func cmdQueueHookRetiredPrint(c *cmd) {
831 c.params = "id"
832 c.help = `Print details of a webhook from the retired queue.
833
834The retired webhook is printed to stdout as JSON.
835`
836 args := c.Parse()
837 if len(args) != 1 {
838 c.Usage()
839 }
840 mustLoadConfig()
841 ctlcmdQueueHookRetiredPrint(xctl(), args[0])
842}
843
844func ctlcmdQueueHookRetiredPrint(ctl *ctl, id string) {
845 ctl.xwrite("queuehookretiredprint")
846 ctl.xwrite(id)
847 ctl.xreadok()
848 if _, err := io.Copy(os.Stdout, ctl.reader()); err != nil {
849 log.Fatalf("%s", err)
850 }
851}
852