/* cyradm.c -- Cyrus administrative client * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The name "Carnegie Mellon University" must not be used to * endorse or promote products derived from this software without * prior written permission. For permission or any other legal * details, please contact * Office of Technology Transfer * Carnegie Mellon University * 5000 Forbes Avenue * Pittsburgh, PA 15213-3890 * (412) 268-4387, fax: (412) 268-7395 * tech-transfer@andrew.cmu.edu * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by Computing Services * at Carnegie Mellon University (http://www.cmu.edu/computing/)." * * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ /* $Id: cyradm.c,v 1.1.1.1 2003/11/06 21:14:17 dasenbro Exp $ */ #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include "imclient.h" #include "imparse.h" #include "tcl.h" #include "xmalloc.h" struct admconn { struct imclient *imclient; int cmd_done; int cmd_result; Tcl_Interp *interp; }; /* Forward decls */ int Cyradm_CyradmCmd(), Cyradm_ConnCmd(); static void Cyradm_DeleteConn(); static int cmd_authenticate(), cmd_listmailbox(); static int cmd_deleteaclmailbox(), cmd_listaclmailbox(), cmd_setaclmailbox(); static int cmd_setquota(), cmd_listquota(), cmd_listquotaroot(); /* * Initialize the cyradm package */ int Cyradm_Init(Tcl_Interp *interp) { Tcl_CreateCommand(interp, "cyradm", Cyradm_CyradmCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; } /* * The cyradm class command */ int Cyradm_CyradmCmd(clientData, interp, argc, argv) ClientData clientData; Tcl_Interp *interp; int argc; char **argv; { char *connection, *hostname, *port = 0; struct admconn *newconn; static struct admconn zeroconn; int r; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " option [arg...]\"", (char *) NULL); return TCL_ERROR; } if (strcmp(argv[1], "connect")) { Tcl_AppendResult(interp, "bad option \"", argv[1], "\", must be connect", (char *) NULL); return TCL_ERROR; } if (argc < 3 || argc > 5) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " connect connection [hostname port]\"", (char *) NULL); return TCL_ERROR; } connection = argv[2]; if (argc < 4) { hostname = connection; } else { hostname = argv[3]; if (argc == 5) { port = argv[4]; } } newconn = (struct admconn *)xmalloc(sizeof(struct admconn)); *newconn = zeroconn; r = imclient_connect(&newconn->imclient, hostname, port); if (r) { if (r == -1) { interp->result = "unknown host"; return TCL_ERROR; } if (r == -2) { interp->result = "unknown service"; return TCL_ERROR; } errno = r; Tcl_AppendResult(interp, "couldn't connect to ", hostname, ": ", Tcl_PosixError(interp), (char *) NULL); return TCL_ERROR; } /* XXX register callbacks, esp. OK/NO/BAD/BYE */ imclient_addcallback(newconn->imclient, "OK", CALLBACK_NOLITERAL, (void (*)()) 0, (void *)0, "NO", CALLBACK_NOLITERAL, (void (*)()) 0, (void *)0, "BAD", CALLBACK_NOLITERAL, (void (*)()) 0, (void *)0, "BYE", CALLBACK_NOLITERAL, (void (*)()) 0, (void *)0, "LIST", 0, (void (*)()) 0, (void *)0, "LSUB", 0, (void (*)()) 0, (void *)0, "ACL", 0, (void (*)()) 0, (void *)0, "QUOTA", 0, (void (*)()) 0, (void *)0, "QUOTAROOT", 0, (void (*)()) 0, (void *)0, (char *)0); Tcl_CreateCommand(interp, connection, Cyradm_ConnCmd, (ClientData) newconn, (Tcl_CmdDeleteProc *) Cyradm_DeleteConn); return TCL_OK; } /* * Connection deletion callback */ static void Cyradm_DeleteConn(clientData) ClientData clientData; { struct admconn *conn = (struct admconn *)clientData; imclient_close(conn->imclient); free((char *) conn); } /* * IMAP command completion callback */ static void callback_finish(imclient, rock, reply) struct imclient *imclient; void *rock; struct imclient_reply *reply; { struct admconn *conn = (struct admconn *)rock; conn->cmd_done++; if (!strcmp(reply->keyword, "OK")) { return; } conn->cmd_result = TCL_ERROR; if (!strcmp(reply->keyword, "NO")) { Tcl_ResetResult(conn->interp); Tcl_AppendResult(conn->interp, "command failed: ", reply->text, (char *) NULL); } else if (!strcmp(reply->keyword, "BAD")) { Tcl_ResetResult(conn->interp); Tcl_AppendResult(conn->interp, "server does not support operation: ", reply->text, (char *) NULL); } else if (!strcmp(reply->keyword, "EOF")) { Tcl_SetResult(conn->interp, "server connection closed", TCL_STATIC); } else { Tcl_SetResult(conn->interp, "unknown result error type", TCL_STATIC); } } /* * Connection object command */ int Cyradm_ConnCmd(clientData, interp, argc, argv) ClientData clientData; Tcl_Interp *interp; int argc; char **argv; { struct admconn *conn = (struct admconn *)clientData; int numcmd = 1; conn->cmd_done = 0; conn->cmd_result = TCL_OK; conn->interp = interp; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " option [arg...]\"", (char *) NULL); return TCL_ERROR; } switch (argv[1][0]) { case 'a': if (!strcmp(argv[1], "authenticate")) { return cmd_authenticate(conn, interp, argc, argv); } goto badoption; case 'c': if (!strcmp(argv[1], "createmailbox")) { if (argc < 3 || argc > 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " createmailbox mailbox [partition]\"", (char *) NULL); return TCL_ERROR; } imclient_send(conn->imclient, callback_finish, (void *)conn, "CREATE %s%a%a", argv[2], argv[3] ? " " : "", argv[3] ? argv[3] : ""); break; } goto badoption; case 'd': if (!strcmp(argv[1], "deletemailbox")) { if (argc < 3 || argc > 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " deletemailbox mailbox [host]\"", (char *) NULL); return TCL_ERROR; } if (argc == 4) { interp->result = "host argument only supported in IMSP"; return TCL_ERROR; } imclient_send(conn->imclient, callback_finish, (void *)conn, "DELETE %s%a%a", argv[2], argv[3] ? " " : "", argv[3] ? argv[3] : ""); break; } if (!strcmp(argv[1], "deleteaclmailbox")) { numcmd = cmd_deleteaclmailbox(conn, interp, argc, argv); if (numcmd < 0) return TCL_ERROR; break; } goto badoption; case 'l': if (!strcmp(argv[1], "listaclmailbox")) { numcmd = cmd_listaclmailbox(conn, interp, argc, argv); if (numcmd < 0) return TCL_ERROR; break; } if (!strcmp(argv[1], "listmailbox")) { numcmd = cmd_listmailbox(conn, interp, argc, argv); if (numcmd < 0) return TCL_ERROR; break; } if (!strcmp(argv[1], "listquota")) { numcmd = cmd_listquota(conn, interp, argc, argv); if (numcmd < 0) return TCL_ERROR; break; } if (!strcmp(argv[1], "listquotaroot")) { numcmd = cmd_listquotaroot(conn, interp, argc, argv); if (numcmd < 0) return TCL_ERROR; break; } goto badoption; case 'r': if (!strcmp(argv[1], "renamemailbox")) { if (argc < 4 || argc > 5) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " renamemailbox old new [partition]\"", (char *) NULL); return TCL_ERROR; } imclient_send(conn->imclient, callback_finish, (void *)conn, "RENAME %s %s%a%a", argv[2], argv[3], argv[4] ? " " : "", argv[4] ? argv[4] : ""); break; } goto badoption; case 's': if (!strcmp(argv[1], "servername")) { if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " servername\"", (char *) NULL); return TCL_ERROR; } Tcl_SetResult(conn->interp, imclient_servername(conn->imclient), TCL_STATIC); return TCL_OK; } if (!strcmp(argv[1], "setaclmailbox")) { numcmd = cmd_setaclmailbox(conn, interp, argc, argv); if (numcmd < 0) return TCL_ERROR; break; } if (!strcmp(argv[1], "setquota")) { numcmd = cmd_setquota(conn, interp, argc, argv); if (numcmd < 0) return TCL_ERROR; break; } goto badoption; badoption: default: Tcl_AppendResult(interp, "bad option \"", argv[1], "\", must be authenticate, createmailbox, ", "deletemailbox, deleteaclmailbox, ", "listaclmailbox, listmailbox, listquota, listquotaroot, ", "renamemailbox, ", "setaclmailbox, or setquota", (char *) NULL); return TCL_ERROR; } while (conn->cmd_done < numcmd) { imclient_processoneevent(conn->imclient); } return conn->cmd_result; } /*********************** * Parse a mech list of the form: ... AUTH=foo AUTH=bar ... * * Return: string with mechs seperated by spaces * ***********************/ typedef struct capabilies_s { char *mechs; /* 0 = false; 1 = true */ int starttls; int logindisabled; } capabilities_t; static capabilities_t *parsecapabilitylist(char *str) { char *tmp; int num=0; capabilities_t *ret=(capabilities_t *) xmalloc(sizeof(capabilities_t)); ret->mechs = (char *)xmalloc(strlen(str)+1); ret->starttls=0; ret->logindisabled=0; /* check for stattls */ if (strstr(str,"STARTTLS")!=NULL) { ret->starttls=1; } /* check for login being disabled */ if (strstr(str,"LOGINDISABLED")!=NULL) { ret->logindisabled=1; } strcpy(ret->mechs,""); while ((tmp=strstr(str,"AUTH="))!=NULL) { char *end=tmp+5; tmp+=5; while(((*end)!=' ') && ((*end)!='\0')) end++; (*end)='\0'; /* add entry to list */ if (num>0) strcat(ret->mechs," "); strcat(ret->mechs, tmp); num++; /* reset the string */ str=end+1; } return ret; } /* * IMAP command completion callback */ static void callback_capability(struct imclient *imclient, void *rock, struct imclient_reply *reply) { char *s; capabilities_t **caps = (capabilities_t **) rock; s = reply->text; *caps = parsecapabilitylist(s); } /* * Use the IMAP login command */ static int cmd_login(struct admconn *conn, char *userid, char *pass, int passlen, int tls_layer, int logindisabled) { if (logindisabled==1) { printf("Login Disabled. Aborting\n"); return -1; } if (pass==NULL) { /* if (tls_layer==0) printf("Warning: About to send password cleartext (ctrl-c to abort)\n"); */ printf("Password: "); pass = getpass(""); passlen = strlen(pass); } imclient_send(conn->imclient, callback_finish, (void *)conn, "LOGIN %s %s", userid, passlen, pass); conn->cmd_done = 0; while (!conn->cmd_done) { imclient_processoneevent(conn->imclient); } return conn->cmd_result; } /* * Perform the authenticate subcommand */ static int cmd_authenticate(conn, interp, argc, argv) struct admconn *conn; Tcl_Interp *interp; int argc; char **argv; { char *pwcommand = 0; char *user = 0; char *p; int r = 0; int minssf=0; /* default to allow any security layer */ int maxssf=10000; char *mech = NULL; char *tls_keyfile = ""; capabilities_t *capabilitylist; int tls_layer = 0; /* skip over command & subcommand */ argv += 2; /* parse switches */ while (argv[0]) { if (!strcmp(argv[0], "-pwcommand")) { if (!argv[1]) break; pwcommand = *++argv; } else if (!strcmp(argv[0], "-user")) { if (!argv[1]) break; user = *++argv; } else if (!strcmp(argv[0], "-layers")) { if (!argv[1]) break; maxssf = atoi(*++argv); } else if (!strcmp(argv[0], "-mech")) { if (!argv[1]) break; mech = *++argv; } else if (!strcmp(argv[0], "-tlskey")) { if (!argv[1]) break; tls_keyfile = *++argv; } else if (!strcmp(argv[0], "-notls")) { tls_keyfile = NULL; } argv++; } if (*argv) { #ifdef HAVE_SSL Tcl_AppendResult(interp, "incorrect args: should be \"", argv[0], " authenticate ", "[-pwcommand string] [-user user] ", "[-layers #] [-mech mechname]\"", (char *) NULL); #else /* HAVE_SSL */ Tcl_AppendResult(interp, "incorrect args: should be \"", argv[0], " authenticate ", "[-pwcommand string] [-user user] ", "[-layers #] [-mech mechname] [-tlskey keyfile] [-notls]\"", (char *) NULL); #endif /* HAVE_SSL */ return TCL_ERROR; } if (!user) { user = xmalloc(sizeof(char) * 1024); strcpy(user, getpwuid(getuid())->pw_name); } imclient_addcallback(conn->imclient, "CAPABILITY", 0, callback_capability, (void *) &capabilitylist, (char *) 0); imclient_send(conn->imclient, callback_finish, (void *) conn, "CAPABILITY"); while (!conn->cmd_done) { imclient_processoneevent(conn->imclient); } #ifdef HAVE_SSL /* starttls unless user told us not to */ if (capabilitylist->starttls == 1) { if (tls_keyfile!=NULL) { imclient_starttls(conn->imclient, 10, tls_keyfile, tls_keyfile, &tls_layer); /* ask for capabilities again */ imclient_addcallback(conn->imclient, "CAPABILITY", 0, callback_capability, (void *) &capabilitylist, (char *) 0); imclient_send(conn->imclient, callback_finish, (void *) conn, "CAPABILITY"); conn->cmd_done = 0; while (!conn->cmd_done) { imclient_processoneevent(conn->imclient); } } } #endif /* HAVE_SSL */ if (!pwcommand) { r = imclient_authenticate(conn->imclient, mech ? mech : capabilitylist->mechs, "imap", user, minssf, maxssf); if (r == SASL_NOMECH) { r = cmd_login(conn, user, NULL, 0, tls_layer, capabilitylist->logindisabled); } } if (pwcommand) { Tcl_DString command; int comc; char **comv; /* Expand the %-escapes in pwcommand */ Tcl_DStringInit(&command); while ((p = strchr(pwcommand, '%'))!=NULL) { Tcl_DStringAppend(&command, pwcommand, p - pwcommand); switch (*++p) { case '%': Tcl_DStringAppend(&command, p, 1); break; case 'h': Tcl_DStringAppendElement(&command, imclient_servername(conn->imclient)); break; case 'u': if (!user) user = ""; Tcl_DStringAppendElement(&command, user); break; default: Tcl_DStringFree(&command); Tcl_AppendResult(interp, "invalid %-sequence in pwcommand", (char *) NULL); return TCL_ERROR; } pwcommand = p+1; } Tcl_DStringAppend(&command, pwcommand, -1); r = Tcl_GlobalEval(interp, Tcl_DStringValue(&command)); Tcl_DStringFree(&command); if (r) return r; if (Tcl_SplitList(interp, interp->result, &comc, &comv)) { return TCL_ERROR; } Tcl_ResetResult(interp); if (comc != 2) { Tcl_SetResult(interp, "pwcommand script did not return a list with two elements", TCL_STATIC); return TCL_ERROR; } r = cmd_login(conn, comv[0], comv[1], strlen(comv[1]), tls_layer, capabilitylist->logindisabled); } if (r) { interp->result = "authentication failed"; return TCL_ERROR; } return TCL_OK; } struct mailboxdata { Tcl_DString data; }; /* * Callback to deal with untagged LIST/LSUB data */ static void callback_list(imclient, rock, reply) struct imclient *imclient; void *rock; struct imclient_reply *reply; { struct mailboxdata *mailboxdata = (struct mailboxdata *)rock; char *s, *end; char *mailbox, *attributes, *separator = NULL; int c; s = reply->text; if (*s++ != '(') return; end = strchr(s, ')'); if (!end) return; attributes = s; s = end; *s++ = '\0'; if (*s++ != ' ') return; if (*s == 'N') { if (s[1] != 'I' || s[2] != 'L') return; separator = ""; s += 3; } else if (*s == '\"') { s++; if (*s == '\\') s++; separator = s++; if (*s != '\"') return; *s++ = '\0'; } if (*s++ != ' ') return; c = imparse_astring(&s, &mailbox); if (c != '\0') return; Tcl_DStringStartSublist(&mailboxdata->data); Tcl_DStringAppendElement(&mailboxdata->data, mailbox); Tcl_DStringStartSublist(&mailboxdata->data); for (s = attributes; (end = strchr(s, ' '))!=NULL ; s = end+1) { *s = '\0'; Tcl_DStringAppendElement(&mailboxdata->data, s); } Tcl_DStringAppendElement(&mailboxdata->data, s); Tcl_DStringEndSublist(&mailboxdata->data); Tcl_DStringAppendElement(&mailboxdata->data, separator); Tcl_DStringEndSublist(&mailboxdata->data); } /* * Perform the listmailbox subcommand */ static int cmd_listmailbox(conn, interp, argc, argv) struct admconn *conn; Tcl_Interp *interp; int argc; char **argv; { char *command = argv[0]; struct mailboxdata mailboxdata; int subscribed = 0; char *reference = ""; argc -= 2; argv += 2; while (argc > 0 && argv[0][0] == '-') { argc--; argv++; if (!strcmp(argv[-1], "--")) break; if (!strcmp(argv[-1], "-subscribed")) subscribed = 1; else { Tcl_AppendResult(interp, "unrecognized switch: should be \"", command, " listmailbox [-subscribed|--] pattern [reference]\"", (char *) NULL); return -1; } } if (argc < 1 || argc > 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", command, " listmailbox [-subscribed|--] pattern [reference]\"", (char *) NULL); return -1; } if (argc == 2) reference = argv[1]; Tcl_DStringInit(&mailboxdata.data); imclient_addcallback(conn->imclient, subscribed ? "LSUB" : "LIST", 0, callback_list, (void *)&mailboxdata, (char *)0); imclient_send(conn->imclient, callback_finish, (void *)conn, "%a %s %s", subscribed ? "LSUB" : "LIST", reference, argv[0]); while (!conn->cmd_done) { imclient_processoneevent(conn->imclient); } if (!conn->cmd_result) { Tcl_DStringResult(interp, &mailboxdata.data); } imclient_addcallback(conn->imclient, subscribed ? "LSUB" : "LIST", 0, (void (*)()) 0, (void *)0, (char *)0); return 0; } /* * Perform the deleteaclmailbox subcommand */ static int cmd_deleteaclmailbox(conn, interp, argc, argv) struct admconn *conn; Tcl_Interp *interp; int argc; char **argv; { int i, num; char *mailbox; if (argc < 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " deleteaclmailbox mailbox id [id]...\"", (char *) NULL); return -1; } mailbox = argv[2]; argv += 3; num = argc - 3; for (i = 0; i < num; i++) { imclient_send(conn->imclient, callback_finish, (void *)conn, "DELETEACL MAILBOX %s %s", mailbox, argv[i]); } return num; } /* * Perform the setaclmailbox subcommand */ static int cmd_setaclmailbox(conn, interp, argc, argv) struct admconn *conn; Tcl_Interp *interp; int argc; char **argv; { int i, num; char *mailbox; char *rights; /* XXX doesn't do -clear */ if (argc < 5 || !(argc & 1)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " setaclmailbox mailbox id rights [id rights]...\"", (char *) NULL); return -1; } mailbox = argv[2]; argv += 3; num = (argc - 3)/2; for (i = 0; i < num; i++) { rights = argv[1]; if (!strcasecmp(rights, "none")) rights = ""; else if (!strcasecmp(rights, "read")) rights = "lrs"; else if (!strcasecmp(rights, "post")) rights = "lrsp"; else if (!strcasecmp(rights, "append")) rights = "lrsip"; else if (!strcasecmp(rights, "write")) rights = "lrswipcd"; else if (!strcasecmp(rights, "all")) rights = "lrswipcda"; imclient_send(conn->imclient, callback_finish, (void *)conn, "SETACL %s %s %s", mailbox, argv[0], rights); argv += 2; } return num; } struct acldata { char *option; char *object; Tcl_DString data; }; /* * Callback to deal with untagged ACL data */ static void callback_acl(imclient, rock, reply) struct imclient *imclient; void *rock; struct imclient_reply *reply; { struct acldata *acldata = (struct acldata *)rock; char *s; char *val, *identifier, *rights; int c; s = reply->text; c = imparse_astring(&s, &val); if (strcasecmp(val, acldata->object) != 0) return; if (c != '\0' && c != ' ') return; while (c == ' ') { c = imparse_astring(&s, &identifier); if (c != ' ') return; c = imparse_astring(&s, &rights); if (c != '\0' && c != ' ') return; Tcl_DStringAppendElement(&acldata->data, identifier); Tcl_DStringAppendElement(&acldata->data, rights); } } /* * Perform the listaclmailbox subcommand */ static int cmd_listaclmailbox(conn, interp, argc, argv) struct admconn *conn; Tcl_Interp *interp; int argc; char **argv; { struct acldata acldata; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " listaclmailbox mailbox\"", (char *) NULL); return -1; } acldata.option = "MAILBOX"; acldata.object = argv[2]; Tcl_DStringInit(&acldata.data); imclient_addcallback(conn->imclient, "ACL", 0, callback_acl, (void *)&acldata, (char *)0); imclient_send(conn->imclient, callback_finish, (void *)conn, "GETACL %s", argv[2]); while (!conn->cmd_done) { imclient_processoneevent(conn->imclient); } if (!conn->cmd_result) { Tcl_DStringResult(interp, &acldata.data); } imclient_addcallback(conn->imclient, "ACL", 0, (void (*)()) 0, (void *)0, (char *)0); return 0; } /* * Perform the setquota subcommand */ static int cmd_setquota(conn, interp, argc, argv) struct admconn *conn; Tcl_Interp *interp; int argc; char **argv; { int i, num; char *mailbox; if (argc < 3 || !(argc & 1)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " setquota mailbox [limit num]...\"", (char *) NULL); return -1; } mailbox = argv[2]; argv += 3; num = (argc - 3)/2; for (i = 0; i < num; i++) { if (!imparse_isatom(argv[2*i])) { Tcl_AppendResult(interp, "invalid quota resource '", argv[2*i], "'", (char *) NULL); return -1; } if (!imparse_isnumber(argv[2*i+1])) { Tcl_AppendResult(interp, "non-numeric quota value '", argv[2*i+1], "'", (char *) NULL); return -1; } } imclient_send(conn->imclient, callback_finish, (void *)conn, "SETQUOTA %s (%v)", mailbox, argv); return 1; } struct quotadata { char *mailbox; Tcl_DString quotaroots; Tcl_DString data; }; /* * Callback to deal with untagged QUOTAROOT data */ static void callback_quotaroot(imclient, rock, reply) struct imclient *imclient; void *rock; struct imclient_reply *reply; { struct quotadata *quotadata = (struct quotadata *)rock; char *s; char *val; int c; s = reply->text; c = imparse_astring(&s, &val); if (c != ' ' || strcasecmp(val, quotadata->mailbox) != 0) return; Tcl_DStringFree("adata->quotaroots); Tcl_DStringAppend("adata->quotaroots, s, -1); } static void callback_quota(imclient, rock, reply) struct imclient *imclient; void *rock; struct imclient_reply *reply; { struct quotadata *quotadata = (struct quotadata *)rock; char *s, *end; char *root; int c; s = reply->text; c = imparse_astring(&s, &root); if (c != ' ') return; if (*s++ != '(') return; end = strchr(s, ')'); if (!end || end[1]) return; *end = '\0'; Tcl_DStringAppendElement("adata->data, root); Tcl_DStringAppendElement("adata->data, s); } /* * Perform the listquota subcommand */ static int cmd_listquota(conn, interp, argc, argv) struct admconn *conn; Tcl_Interp *interp; int argc; char **argv; { int i; struct quotadata quotadata; int quotac; char **quotav; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " listquota root\"", (char *) NULL); return -1; } Tcl_DStringInit("adata.data); imclient_addcallback(conn->imclient, "QUOTA", 0, callback_quota, (void *)"adata, (char *)0); imclient_send(conn->imclient, callback_finish, (void *)conn, "GETQUOTA %s", argv[2]); while (!conn->cmd_done) { imclient_processoneevent(conn->imclient); } imclient_addcallback(conn->imclient, "QUOTA", 0, (void (*)()) 0, (void *)0, (char *)0); if (!conn->cmd_result) { if (Tcl_SplitList(interp, Tcl_DStringValue("adata.data), "ac, "av)) { Tcl_DStringFree("adata.data); return -1; } for (i = 0; i < quotac; i += 2) { if (!strcasecmp(argv[2], quotav[i])) { Tcl_SetResult(interp, quotav[i+1], TCL_VOLATILE); } } free((char *)quotav); Tcl_DStringFree("adata.data); } return 0; } /* * Perform the listquotaroot subcommand */ static int cmd_listquotaroot(conn, interp, argc, argv) struct admconn *conn; Tcl_Interp *interp; int argc; char **argv; { int i, j; struct quotadata quotadata; int quotac, rootc; char **quotav, **rootv; Tcl_DString result; char *quotaforroot; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " listquotaroot mailbox\"", (char *) NULL); return -1; } quotadata.mailbox = argv[2]; Tcl_DStringInit("adata.quotaroots); Tcl_DStringInit("adata.data); imclient_addcallback(conn->imclient, "QUOTA", 0, callback_quota, (void *)"adata, "QUOTAROOT", 0, callback_quotaroot, (void *)"adata, (char *)0); imclient_send(conn->imclient, callback_finish, (void *)conn, "GETQUOTAROOT %s", argv[2]); while (!conn->cmd_done) { imclient_processoneevent(conn->imclient); } imclient_addcallback(conn->imclient, "QUOTA", 0, (void (*)()) 0, (void *)0, "QUOTAROOT", 0, (void (*)()) 0, (void *)0, (char *)0); if (!conn->cmd_result) { if (Tcl_SplitList(interp, Tcl_DStringValue("adata.data), "ac, "av) || Tcl_SplitList(interp, Tcl_DStringValue("adata.quotaroots), &rootc, &rootv)) { Tcl_DStringFree("adata.data); Tcl_DStringFree("adata.quotaroots); return -1; } Tcl_DStringInit(&result); for (i = 0; i < rootc; i++) { Tcl_DStringStartSublist(&result); Tcl_DStringAppendElement(&result, rootv[i]); quotaforroot = 0; for (j = 0; j < quotac; j += 2) { if (!strcasecmp(rootv[i], quotav[j])) { quotaforroot = quotav[j+1]; } } if (*quotaforroot) { Tcl_DStringAppend(&result, " ", 1); Tcl_DStringAppend(&result, quotaforroot, -1); } Tcl_DStringEndSublist(&result); } Tcl_DStringFree("adata.data); Tcl_DStringFree("adata.quotaroots); free((char *)quotav); free((char *)rootv); Tcl_DStringResult(interp, &result); } return 0; }