indent -kr -i8 -l132 on everything in textclient
[citadel] / textclient / citadel_ipc.c
1 /*
2  * Copyright (c) 1987-2018 by the citadel.org team
3  *
4  *  This program is open source software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License version 3.
6  *
7  *  This program is distributed in the hope that it will be useful,
8  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *  GNU General Public License for more details.
11  */
12
13 #include "textclient.h"
14
15 #ifdef HAVE_OPENSSL
16 static SSL_CTX *ssl_ctx;
17 char arg_encrypt;
18 char rc_encrypt;
19
20 #endif                          /* HAVE_OPENSSL */
21
22 #ifndef INADDR_NONE
23 #define INADDR_NONE 0xffffffff
24 #endif
25
26 static void (*status_hook) (char *s) = NULL;
27 char ctdl_autoetc_dir[PATH_MAX] = "";
28 char file_citadel_rc[PATH_MAX] = "";
29 char ctdl_run_dir[PATH_MAX] = "";
30 char ctdl_etc_dir[PATH_MAX] = "";
31 char ctdl_home_directory[PATH_MAX] = "";
32 char file_citadel_socket[PATH_MAX] = "";
33
34 char *viewdefs[] = {
35         "Messages",
36         "Summary",
37         "Address book",
38         "Calendar",
39         "Tasks"
40 };
41
42 char *axdefs[] = {
43         "Deleted",
44         "New User",
45         "Problem User",
46         "Local User",
47         "Network User",
48         "Preferred User",
49         "Admin",
50         "Admin"
51 };
52
53
54 void CtdlIPC_lock(CtdlIPC * ipc)
55 {
56         if (ipc->network_status_cb)
57                 ipc->network_status_cb(1);
58 }
59
60
61 void CtdlIPC_unlock(CtdlIPC * ipc)
62 {
63         if (ipc->network_status_cb)
64                 ipc->network_status_cb(0);
65 }
66
67
68 char *libcitadelclient_version_string(void)
69 {
70         return "libcitadelclient(unnumbered)";
71 }
72
73
74
75
76 #define COMPUTE_DIRECTORY(SUBDIR) memcpy(dirbuffer,SUBDIR, sizeof dirbuffer);\
77         snprintf(SUBDIR,sizeof SUBDIR,  "%s%s%s%s%s%s%s", \
78                          (home&!relh)?ctdl_home_directory:basedir, \
79              ((basedir!=ctdldir)&(home&!relh))?basedir:"/", \
80              ((basedir!=ctdldir)&(home&!relh))?"/":"", \
81                          relhome, \
82              (relhome[0]!='\0')?"/":"",\
83                          dirbuffer,\
84                          (dirbuffer[0]!='\0')?"/":"");
85
86 #define DBG_PRINT(A) if (dbg==1) fprintf (stderr,"%s : %s \n", #A, A)
87
88
89 void calc_dirs_n_files(int relh, int home, const char *relhome, char *ctdldir, int dbg)
90 {
91         const char *basedir = "";
92         char dirbuffer[PATH_MAX] = "";
93
94         StripSlashes(ctdldir, 1);
95
96 #ifndef HAVE_RUN_DIR
97         basedir = ctdldir;
98 #else
99         basedir = RUN_DIR;
100 #endif
101         COMPUTE_DIRECTORY(ctdl_run_dir);
102         StripSlashes(ctdl_run_dir, 1);
103
104
105 #ifndef HAVE_AUTO_ETC_DIR
106         basedir = ctdldir;
107 #else
108         basedir = AUTO_ETC_DIR;
109 #endif
110         COMPUTE_DIRECTORY(ctdl_autoetc_dir);
111         StripSlashes(ctdl_autoetc_dir, 1);
112
113
114 #ifndef HAVE_ETC_DIR
115         basedir = ctdldir;
116 #else
117         basedir = ETC_DIR;
118 #endif
119         COMPUTE_DIRECTORY(ctdl_etc_dir);
120         StripSlashes(ctdl_etc_dir, 1);
121
122
123
124         snprintf(file_citadel_rc, sizeof file_citadel_rc, "%scitadel.rc", ctdl_etc_dir);
125         StripSlashes(file_citadel_rc, 0);
126
127         snprintf(file_citadel_socket, sizeof file_citadel_socket, "%scitadel.socket", ctdl_run_dir);
128         StripSlashes(file_citadel_socket, 0);
129
130         DBG_PRINT(ctdl_run_dir);
131         DBG_PRINT(file_citadel_socket);
132         DBG_PRINT(ctdl_etc_dir);
133         DBG_PRINT(file_citadel_rc);
134 }
135
136 void setCryptoStatusHook(void (*hook) (char *s))
137 {
138         status_hook = hook;
139 }
140
141 void CtdlIPC_SetNetworkStatusCallback(CtdlIPC * ipc, void (*hook) (int state))
142 {
143         ipc->network_status_cb = hook;
144 }
145
146
147 char instant_msgs = 0;
148
149
150 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes);
151 static void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
152 #ifdef HAVE_OPENSSL
153 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes);
154 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes);
155 static void endtls(SSL * ssl);
156 #endif                          /* HAVE_OPENSSL */
157 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf);
158 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf);
159
160
161
162 const char *svn_revision(void);
163
164 /*
165  * Does nothing.  The server should always return 200.
166  */
167 int CtdlIPCNoop(CtdlIPC * ipc)
168 {
169         char aaa[128];
170
171         return CtdlIPCGenericCommand(ipc, "NOOP", NULL, 0, NULL, NULL, aaa);
172 }
173
174
175 /*
176  * Does nothing interesting.  The server should always return 200
177  * along with your string.
178  */
179 int CtdlIPCEcho(CtdlIPC * ipc, const char *arg, char *cret)
180 {
181         int ret;
182         char *aaa;
183
184         if (!arg)
185                 return -2;
186         if (!cret)
187                 return -2;
188
189         aaa = (char *) malloc((size_t) (strlen(arg) + 6));
190         if (!aaa)
191                 return -1;
192
193         sprintf(aaa, "ECHO %s", arg);
194         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
195         free(aaa);
196         return ret;
197 }
198
199
200 /*
201  * Asks the server to close the connecction.
202  * Should always return 200.
203  */
204 int CtdlIPCQuit(CtdlIPC * ipc)
205 {
206         int ret = 221;          /* Default to successful quit */
207         char aaa[SIZ];
208
209         CtdlIPC_lock(ipc);
210         if (ipc->sock > -1) {
211                 CtdlIPC_putline(ipc, "QUIT");
212                 CtdlIPC_getline(ipc, aaa);
213                 ret = atoi(aaa);
214         }
215 #ifdef HAVE_OPENSSL
216         if (ipc->ssl)
217                 SSL_shutdown(ipc->ssl);
218         ipc->ssl = NULL;
219 #endif
220         if (ipc->sock)
221                 shutdown(ipc->sock, 2); /* Close connection; we're dead */
222         ipc->sock = -1;
223         CtdlIPC_unlock(ipc);
224         return ret;
225 }
226
227
228 /*
229  * Asks the server to log out.  Should always return 200, even if no user
230  * was logged in.  The user will not be logged in after this!
231  */
232 int CtdlIPCLogout(CtdlIPC * ipc)
233 {
234         int ret;
235         char aaa[SIZ];
236
237         CtdlIPC_lock(ipc);
238         CtdlIPC_putline(ipc, "LOUT");
239         CtdlIPC_getline(ipc, aaa);
240         ret = atoi(aaa);
241         CtdlIPC_unlock(ipc);
242         return ret;
243 }
244
245
246 /*
247  * First stage of authentication - pass the username.  Returns 300 if the
248  * username is able to log in, with the username correctly spelled in cret.
249  * Returns various 500 error codes if the user doesn't exist, etc.
250  */
251 int CtdlIPCTryLogin(CtdlIPC * ipc, const char *username, char *cret)
252 {
253         int ret;
254         char *aaa;
255
256         if (!username)
257                 return -2;
258         if (!cret)
259                 return -2;
260
261         aaa = (char *) malloc((size_t) (strlen(username) + 6));
262         if (!aaa)
263                 return -1;
264
265         sprintf(aaa, "USER %s", username);
266         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
267         free(aaa);
268         return ret;
269 }
270
271
272 /*
273  * Second stage of authentication - provide password.  The server returns
274  * 200 and several arguments in cret relating to the user's account.
275  */
276 int CtdlIPCTryPassword(CtdlIPC * ipc, const char *passwd, char *cret)
277 {
278         int ret;
279         char *aaa;
280
281         if (!passwd)
282                 return -2;
283         if (!cret)
284                 return -2;
285
286         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
287         if (!aaa)
288                 return -1;
289
290         sprintf(aaa, "PASS %s", passwd);
291         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
292         free(aaa);
293         return ret;
294 }
295
296
297 /*
298  * Second stage of authentication - provide password.  The server returns
299  * 200 and several arguments in cret relating to the user's account.
300  */
301 int CtdlIPCTryApopPassword(CtdlIPC * ipc, const char *response, char *cret)
302 {
303         int ret;
304         char *aaa;
305
306         if (!response)
307                 return -2;
308         if (!cret)
309                 return -2;
310
311         aaa = (char *) malloc((size_t) (strlen(response) + 6));
312         if (!aaa)
313                 return -1;
314
315         sprintf(aaa, "PAS2 %s", response);
316         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
317         free(aaa);
318         return ret;
319 }
320
321
322 /*
323  * Create a new user.  This returns 200 plus the same arguments as TryPassword
324  * if selfservice is nonzero, unless there was a problem creating the account.
325  * If selfservice is zero, creates a new user but does not log out the existing
326  * user - intended for use by system administrators to create accounts on
327  * behalf of other users.
328  */
329 int CtdlIPCCreateUser(CtdlIPC * ipc, const char *username, int selfservice, char *cret)
330 {
331         int ret;
332         char *aaa;
333
334         if (!username)
335                 return -2;
336         if (!cret)
337                 return -2;
338
339         aaa = (char *) malloc((size_t) (strlen(username) + 6));
340         if (!aaa)
341                 return -1;
342
343         sprintf(aaa, "%s %s", selfservice ? "NEWU" : "CREU", username);
344         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
345         free(aaa);
346         return ret;
347 }
348
349
350 /*
351  * Changes the user's password.  Returns 200 if changed, errors otherwise.
352  */
353 int CtdlIPCChangePassword(CtdlIPC * ipc, const char *passwd, char *cret)
354 {
355         int ret;
356         char *aaa;
357
358         if (!passwd)
359                 return -2;
360         if (!cret)
361                 return -2;
362
363         aaa = (char *) malloc((size_t) (strlen(passwd) + 6));
364         if (!aaa)
365                 return -1;
366
367         sprintf(aaa, "SETP %s", passwd);
368         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
369         free(aaa);
370         return ret;
371 }
372
373
374 /* LKRN */
375 /* Caller must free the march list */
376 /* Room types are defined in enum RoomList; keep these in sync! */
377 /* floor is -1 for all, or floornum */
378 int CtdlIPCKnownRooms(CtdlIPC * ipc, enum RoomList which, int floor, struct march **listing, char *cret)
379 {
380         int ret;
381         struct march *march = NULL;
382         static char *proto[] = { "LKRA", "LKRN", "LKRO", "LZRM", "LRMS", "LPRM" };
383         char aaa[SIZ];
384         char *bbb = NULL;
385         size_t bbb_len;
386
387         if (!listing)
388                 return -2;
389         if (*listing)
390                 return -2;      /* Free the listing first */
391         if (!cret)
392                 return -2;
393         /* if (which < 0 || which > 4) return -2; */
394         if (floor < -1)
395                 return -2;      /* Can't validate upper bound, sorry */
396
397         sprintf(aaa, "%s %d", proto[which], floor);
398         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
399         if (ret / 100 == 1) {
400                 struct march *mptr;
401
402                 while (bbb && strlen(bbb)) {
403                         int a;
404
405                         extract_token(aaa, bbb, 0, '\n', sizeof aaa);
406                         a = strlen(aaa);
407                         memmove(bbb, bbb + a + 1, strlen(bbb) - a);
408                         mptr = (struct march *) malloc(sizeof(struct march));
409                         if (mptr) {
410                                 mptr->next = NULL;
411                                 extract_token(mptr->march_name, aaa, 0, '|', sizeof mptr->march_name);
412                                 mptr->march_flags = (unsigned int) extract_int(aaa, 1);
413                                 mptr->march_floor = (char) extract_int(aaa, 2);
414                                 mptr->march_order = (char) extract_int(aaa, 3);
415                                 mptr->march_flags2 = (unsigned int) extract_int(aaa, 4);
416                                 mptr->march_access = (char) extract_int(aaa, 5);
417                                 if (march == NULL)
418                                         march = mptr;
419                                 else {
420                                         struct march *mptr2;
421
422                                         mptr2 = march;
423                                         while (mptr2->next != NULL)
424                                                 mptr2 = mptr2->next;
425                                         mptr2->next = mptr;
426                                 }
427                         }
428                 }
429         }
430         *listing = march;
431         if (bbb)
432                 free(bbb);
433         return ret;
434 }
435
436
437 /* GETU */
438 /* Caller must free the struct ctdluser; caller may pass an existing one */
439 int CtdlIPCGetConfig(CtdlIPC * ipc, struct ctdluser **uret, char *cret)
440 {
441         int ret;
442
443         if (!cret)
444                 return -2;
445         if (!uret)
446                 return -2;
447         if (!*uret)
448                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
449         if (!*uret)
450                 return -1;
451
452         ret = CtdlIPCGenericCommand(ipc, "GETU", NULL, 0, NULL, NULL, cret);
453         if (ret / 100 == 2) {
454                 uret[0]->flags = extract_int(cret, 2);
455         }
456         return ret;
457 }
458
459
460 /* SETU */
461 int CtdlIPCSetConfig(CtdlIPC * ipc, struct ctdluser *uret, char *cret)
462 {
463         char aaa[48];
464
465         if (!uret)
466                 return -2;
467         if (!cret)
468                 return -2;
469
470         sprintf(aaa, "SETU 80|24|%d", uret->flags);
471         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
472 }
473
474
475 /* RENU */
476 int CtdlIPCRenameUser(CtdlIPC * ipc, char *oldname, char *newname, char *cret)
477 {
478         int ret;
479         char cmd[256];
480
481         if (!oldname)
482                 return -2;
483         if (!newname)
484                 return -2;
485         if (!cret)
486                 return -2;
487
488         snprintf(cmd, sizeof cmd, "RENU %s|%s", oldname, newname);
489         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
490         return ret;
491 }
492
493
494 /* GOTO */
495 int CtdlIPCGotoRoom(CtdlIPC * ipc, const char *room, const char *passwd, struct ctdlipcroom **rret, char *cret)
496 {
497         int ret;
498         char *aaa;
499
500         if (!cret)
501                 return -2;
502         if (!rret)
503                 return -2;
504         if (!*rret)
505                 *rret = (struct ctdlipcroom *) calloc(1, sizeof(struct ctdlipcroom));
506         if (!*rret)
507                 return -1;
508
509         if (passwd) {
510                 aaa = (char *) malloc(strlen(room) + strlen(passwd) + 7);
511                 if (!aaa) {
512                         free(*rret);
513                         return -1;
514                 }
515                 sprintf(aaa, "GOTO %s|%s", room, passwd);
516         } else {
517                 aaa = (char *) malloc(strlen(room) + 6);
518                 if (!aaa) {
519                         free(*rret);
520                         return -1;
521                 }
522                 sprintf(aaa, "GOTO %s", room);
523         }
524         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
525         if (ret / 100 == 2) {
526                 extract_token(rret[0]->RRname, cret, 0, '|', sizeof rret[0]->RRname);
527                 rret[0]->RRunread = extract_long(cret, 1);
528                 rret[0]->RRtotal = extract_long(cret, 2);
529                 rret[0]->RRinfoupdated = extract_int(cret, 3);
530                 rret[0]->RRflags = extract_int(cret, 4);
531                 rret[0]->RRhighest = extract_long(cret, 5);
532                 rret[0]->RRlastread = extract_long(cret, 6);
533                 rret[0]->RRismailbox = extract_int(cret, 7);
534                 rret[0]->RRaide = extract_int(cret, 8);
535                 rret[0]->RRnewmail = extract_long(cret, 9);
536                 rret[0]->RRfloor = extract_int(cret, 10);
537                 rret[0]->RRcurrentview = extract_int(cret, 11);
538                 rret[0]->RRdefaultview = extract_int(cret, 12);
539                 /* position 13 is a trash folder flag ... irrelevant in this client */
540                 rret[0]->RRflags2 = extract_int(cret, 14);
541         } else {
542                 free(*rret);
543                 *rret = NULL;
544         }
545         free(aaa);
546         return ret;
547 }
548
549
550 /* MSGS */
551 /* which is 0 = all, 1 = old, 2 = new, 3 = last, 4 = first, 5 = gt, 6 = lt */
552 /* whicharg is number of messages, applies to last, first, gt, lt */
553 int CtdlIPCGetMessages(CtdlIPC * ipc, enum MessageList which, int whicharg, const char *mtemplate, unsigned long **mret, char *cret)
554 {
555         int ret;
556         unsigned long count = 0;
557         static char *proto[] = { "ALL", "OLD", "NEW", "LAST", "FIRST", "GT", "LT" };
558         char aaa[33];
559         char *bbb = NULL;
560         size_t bbb_len;
561
562         if (!cret)
563                 return -2;
564         if (!mret)
565                 return -2;
566         if (*mret)
567                 return -2;
568         if (which < 0 || which > 6)
569                 return -2;
570
571         if (which <= 2)
572                 sprintf(aaa, "MSGS %s||%d", proto[which], (mtemplate) ? 1 : 0);
573         else
574                 sprintf(aaa, "MSGS %s|%d|%d", proto[which], whicharg, (mtemplate) ? 1 : 0);
575         if (mtemplate)
576                 count = strlen(mtemplate);
577         ret = CtdlIPCGenericCommand(ipc, aaa, mtemplate, count, &bbb, &bbb_len, cret);
578         if (ret / 100 != 1)
579                 return ret;
580         count = 0;
581         *mret = (unsigned long *) calloc(1, sizeof(unsigned long));
582         if (!*mret)
583                 return -1;
584         while (bbb && strlen(bbb)) {
585                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
586                 remove_token(bbb, 0, '\n');
587                 *mret = (unsigned long *) realloc(*mret, (size_t) ((count + 2) * sizeof(unsigned long)));
588                 if (*mret) {
589                         (*mret)[count++] = atol(aaa);
590                         (*mret)[count] = 0L;
591                 } else {
592                         break;
593                 }
594         }
595         if (bbb)
596                 free(bbb);
597         return ret;
598 }
599
600
601 /* MSG0, MSG2 */
602 int CtdlIPCGetSingleMessage(CtdlIPC * ipc, long msgnum, int headers, int as_mime, struct ctdlipcmessage **mret, char *cret)
603 {
604         int ret;
605         char aaa[SIZ];
606         char *bbb = NULL;
607         size_t bbb_len;
608         int multipart_hunting = 0;
609         char multipart_prefix[128];
610         char encoding[256];
611
612         if (!cret)
613                 return -1;
614         if (!mret)
615                 return -1;
616         if (!*mret)
617                 *mret = (struct ctdlipcmessage *) calloc(1, sizeof(struct ctdlipcmessage));
618         if (!*mret)
619                 return -1;
620         if (!msgnum)
621                 return -1;
622
623         strcpy(encoding, "");
624         strcpy(mret[0]->content_type, "");
625         sprintf(aaa, "MSG%d %ld|%d", as_mime, msgnum, headers);
626         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &bbb, &bbb_len, cret);
627         if (ret / 100 == 1) {
628                 if (as_mime != 2) {
629                         strcpy(mret[0]->mime_chosen, "1");      /* Default chosen-part is "1" */
630                         while (strlen(bbb) > 4 && bbb[4] == '=') {
631                                 extract_token(aaa, bbb, 0, '\n', sizeof aaa);
632                                 remove_token(bbb, 0, '\n');
633
634                                 if (!strncasecmp(aaa, "nhdr=yes", 8))
635                                         mret[0]->nhdr = 1;
636                                 else if (!strncasecmp(aaa, "from=", 5))
637                                         safestrncpy(mret[0]->author, &aaa[5], SIZ);
638                                 else if (!strncasecmp(aaa, "type=", 5))
639                                         mret[0]->type = atoi(&aaa[5]);
640                                 else if (!strncasecmp(aaa, "msgn=", 5))
641                                         safestrncpy(mret[0]->msgid, &aaa[5], SIZ);
642                                 else if (!strncasecmp(aaa, "subj=", 5))
643                                         safestrncpy(mret[0]->subject, &aaa[5], SIZ);
644                                 else if (!strncasecmp(aaa, "rfca=", 5))
645                                         safestrncpy(mret[0]->email, &aaa[5], SIZ);
646                                 else if (!strncasecmp(aaa, "room=", 5))
647                                         safestrncpy(mret[0]->room, &aaa[5], SIZ);
648                                 else if (!strncasecmp(aaa, "rcpt=", 5))
649                                         safestrncpy(mret[0]->recipient, &aaa[5], SIZ);
650                                 else if (!strncasecmp(aaa, "wefw=", 5))
651                                         safestrncpy(mret[0]->references, &aaa[5], SIZ);
652                                 else if (!strncasecmp(aaa, "time=", 5))
653                                         mret[0]->time = atol(&aaa[5]);
654
655                                 /* Multipart/alternative prefix & suffix strings help
656                                  * us to determine which part we want to download.
657                                  */
658                                 else if (!strncasecmp(aaa, "pref=", 5)) {
659                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
660                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
661                                                 ++multipart_hunting;
662                                         }
663                                 } else if (!strncasecmp(aaa, "suff=", 5)) {
664                                         extract_token(multipart_prefix, &aaa[5], 1, '|', sizeof multipart_prefix);
665                                         if (!strcasecmp(multipart_prefix, "multipart/alternative")) {
666                                                 ++multipart_hunting;
667                                         }
668                                 }
669
670                                 else if (!strncasecmp(aaa, "part=", 5)) {
671                                         struct parts *ptr, *chain;
672
673                                         ptr = (struct parts *) calloc(1, sizeof(struct parts));
674                                         if (ptr) {
675
676                                                 /* Fill the buffers for the caller */
677                                                 extract_token(ptr->name, &aaa[5], 0, '|', sizeof ptr->name);
678                                                 extract_token(ptr->filename, &aaa[5], 1, '|', sizeof ptr->filename);
679                                                 extract_token(ptr->number, &aaa[5], 2, '|', sizeof ptr->number);
680                                                 extract_token(ptr->disposition, &aaa[5], 3, '|', sizeof ptr->disposition);
681                                                 extract_token(ptr->mimetype, &aaa[5], 4, '|', sizeof ptr->mimetype);
682                                                 ptr->length = extract_long(&aaa[5], 5);
683                                                 if (!mret[0]->attachments)
684                                                         mret[0]->attachments = ptr;
685                                                 else {
686                                                         chain = mret[0]->attachments;
687                                                         while (chain->next)
688                                                                 chain = chain->next;
689                                                         chain->next = ptr;
690                                                 }
691
692                                                 /* Now handle multipart/alternative */
693                                                 if (multipart_hunting > 0) {
694                                                         if ((!strcasecmp(ptr->mimetype, "text/plain"))
695                                                             || (!strcasecmp(ptr->mimetype, "text/html"))) {
696                                                                 strcpy(mret[0]->mime_chosen, ptr->number);
697                                                         }
698                                                 }
699
700                                         }
701                                 }
702                         }
703                         /* Eliminate "text\n" */
704                         remove_token(bbb, 0, '\n');
705
706                         /* If doing a MIME thing, pull out the extra headers */
707                         if (as_mime == 4) {
708                                 do {
709                                         if (!strncasecmp(bbb, "Content-type:", 13)) {
710                                                 extract_token(mret[0]->content_type, bbb, 0, '\n', sizeof mret[0]->content_type);
711                                                 strcpy(mret[0]->content_type, &mret[0]->content_type[13]);
712                                                 striplt(mret[0]->content_type);
713
714                                                 /* strip out ";charset=" portion.  FIXME do something with
715                                                  * the charset (like... convert it) instead of just throwing
716                                                  * it away
717                                                  */
718                                                 if (strstr(mret[0]->content_type, ";") != NULL) {
719                                                         strcpy(strstr(mret[0]->content_type, ";"), "");
720                                                 }
721
722                                         }
723                                         if (!strncasecmp(bbb, "X-Citadel-MSG4-Partnum:", 23)) {
724                                                 extract_token(mret[0]->mime_chosen, bbb, 0, '\n', sizeof mret[0]->mime_chosen);
725                                                 strcpy(mret[0]->mime_chosen, &mret[0]->mime_chosen[23]);
726                                                 striplt(mret[0]->mime_chosen);
727                                         }
728                                         if (!strncasecmp(bbb, "Content-transfer-encoding:", 26)) {
729                                                 extract_token(encoding, bbb, 0, '\n', sizeof encoding);
730                                                 strcpy(encoding, &encoding[26]);
731                                                 striplt(encoding);
732                                         }
733                                         remove_token(bbb, 0, '\n');
734                                 } while ((bbb[0] != 0) && (bbb[0] != '\n'));
735                                 remove_token(bbb, 0, '\n');
736                         }
737
738
739                 }
740                 if (strlen(bbb)) {
741
742                         if ((!strcasecmp(encoding, "base64")) || (!strcasecmp(encoding, "quoted-printable"))) {
743                                 char *ccc = NULL;
744                                 int bytes_decoded = 0;
745                                 ccc = malloc(strlen(bbb) + 32768);
746                                 if (!strcasecmp(encoding, "base64")) {
747                                         bytes_decoded = CtdlDecodeBase64(ccc, bbb, strlen(bbb));
748                                 } else if (!strcasecmp(encoding, "quoted-printable")) {
749                                         bytes_decoded = CtdlDecodeQuotedPrintable(ccc, bbb, strlen(bbb));
750                                 }
751                                 ccc[bytes_decoded] = 0;
752                                 free(bbb);
753                                 bbb = ccc;
754                         }
755
756                         /* FIXME: Strip trailing whitespace */
757                         bbb = (char *) realloc(bbb, (size_t) (strlen(bbb) + 1));
758
759                 } else {
760                         bbb = (char *) realloc(bbb, 1);
761                         *bbb = '\0';
762                 }
763                 mret[0]->text = bbb;
764         }
765         return ret;
766 }
767
768
769 /* WHOK */
770 int CtdlIPCWhoKnowsRoom(CtdlIPC * ipc, char **listing, char *cret)
771 {
772         int ret;
773         size_t bytes;
774
775         if (!cret)
776                 return -2;
777         if (!listing)
778                 return -2;
779         if (*listing)
780                 return -2;
781
782         ret = CtdlIPCGenericCommand(ipc, "WHOK", NULL, 0, listing, &bytes, cret);
783         return ret;
784 }
785
786
787 /* INFO */
788 int CtdlIPCServerInfo(CtdlIPC * ipc, char *cret)
789 {
790         int ret;
791         size_t bytes;
792         char *listing = NULL;
793         char buf[SIZ];
794
795         if (!cret)
796                 return -2;
797
798         ret = CtdlIPCGenericCommand(ipc, "INFO", NULL, 0, &listing, &bytes, cret);
799         if (ret / 100 == 1) {
800                 int line = 0;
801
802                 while (*listing && strlen(listing)) {
803                         extract_token(buf, listing, 0, '\n', sizeof buf);
804                         remove_token(listing, 0, '\n');
805                         switch (line++) {
806                         case 0:
807                                 ipc->ServInfo.pid = atoi(buf);
808                                 break;
809                         case 1:
810                                 strcpy(ipc->ServInfo.nodename, buf);
811                                 break;
812                         case 2:
813                                 strcpy(ipc->ServInfo.humannode, buf);
814                                 break;
815                         case 3:
816                                 strcpy(ipc->ServInfo.fqdn, buf);
817                                 break;
818                         case 4:
819                                 strcpy(ipc->ServInfo.software, buf);
820                                 break;
821                         case 5:
822                                 ipc->ServInfo.rev_level = atoi(buf);
823                                 break;
824                         case 6:
825                                 strcpy(ipc->ServInfo.site_location, buf);
826                                 break;
827                         case 7:
828                                 strcpy(ipc->ServInfo.sysadm, buf);
829                                 break;
830                         case 9:
831                                 strcpy(ipc->ServInfo.moreprompt, buf);
832                                 break;
833                         case 10:
834                                 ipc->ServInfo.ok_floors = atoi(buf);
835                                 break;
836                         case 11:
837                                 ipc->ServInfo.paging_level = atoi(buf);
838                                 break;
839                         case 13:
840                                 ipc->ServInfo.supports_qnop = atoi(buf);
841                                 break;
842                         case 14:
843                                 ipc->ServInfo.supports_ldap = atoi(buf);
844                                 break;
845                         case 15:
846                                 ipc->ServInfo.newuser_disabled = atoi(buf);
847                                 break;
848                         case 16:
849                                 strcpy(ipc->ServInfo.default_cal_zone, buf);
850                                 break;
851                         case 17:
852                                 ipc->ServInfo.load_avg = atof(buf);
853                                 break;
854                         case 18:
855                                 ipc->ServInfo.worker_avg = atof(buf);
856                                 break;
857                         case 19:
858                                 ipc->ServInfo.thread_count = atoi(buf);
859                                 break;
860                         case 20:
861                                 ipc->ServInfo.has_sieve = atoi(buf);
862                                 break;
863                         case 21:
864                                 ipc->ServInfo.fulltext_enabled = atoi(buf);
865                                 break;
866                         case 22:
867                                 strcpy(ipc->ServInfo.svn_revision, buf);
868                                 break;
869                         case 24:
870                                 ipc->ServInfo.guest_logins = atoi(buf);
871                                 break;
872                         }
873                 }
874
875         }
876         if (listing)
877                 free(listing);
878         return ret;
879 }
880
881
882 /* RDIR */
883 int CtdlIPCReadDirectory(CtdlIPC * ipc, char **listing, char *cret)
884 {
885         int ret;
886         size_t bytes;
887
888         if (!cret)
889                 return -2;
890         if (!listing)
891                 return -2;
892         if (*listing)
893                 return -2;
894
895         ret = CtdlIPCGenericCommand(ipc, "RDIR", NULL, 0, listing, &bytes, cret);
896         return ret;
897 }
898
899
900 /*
901  * Set last-read pointer in this room to msgnum, or 0 for HIGHEST.
902  */
903 int CtdlIPCSetLastRead(CtdlIPC * ipc, long msgnum, char *cret)
904 {
905         int ret;
906         char aaa[64];
907
908         if (!cret)
909                 return -2;
910
911         if (msgnum) {
912                 sprintf(aaa, "SLRP %ld", msgnum);
913         } else {
914                 sprintf(aaa, "SLRP HIGHEST");
915         }
916         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
917         return ret;
918 }
919
920
921 /* INVT */
922 int CtdlIPCInviteUserToRoom(CtdlIPC * ipc, const char *username, char *cret)
923 {
924         int ret;
925         char *aaa;
926
927         if (!cret)
928                 return -2;
929         if (!username)
930                 return -2;
931
932         aaa = (char *) malloc(strlen(username) + 6);
933         if (!aaa)
934                 return -1;
935
936         sprintf(aaa, "INVT %s", username);
937         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
938         free(aaa);
939         return ret;
940 }
941
942
943 /* KICK */
944 int CtdlIPCKickoutUserFromRoom(CtdlIPC * ipc, const char *username, char *cret)
945 {
946         int ret;
947         char *aaa;
948
949         if (!cret)
950                 return -1;
951         if (!username)
952                 return -1;
953
954         aaa = (char *) malloc(strlen(username) + 6);
955
956         sprintf(aaa, "KICK %s", username);
957         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
958         free(aaa);
959         return ret;
960 }
961
962
963 /* GETR */
964 int CtdlIPCGetRoomAttributes(CtdlIPC * ipc, struct ctdlroom **qret, char *cret)
965 {
966         int ret;
967
968         if (!cret)
969                 return -2;
970         if (!qret)
971                 return -2;
972         if (!*qret)
973                 *qret = (struct ctdlroom *) calloc(1, sizeof(struct ctdlroom));
974         if (!*qret)
975                 return -1;
976
977         ret = CtdlIPCGenericCommand(ipc, "GETR", NULL, 0, NULL, NULL, cret);
978         if (ret / 100 == 2) {
979                 extract_token(qret[0]->QRname, cret, 0, '|', sizeof qret[0]->QRname);
980                 extract_token(qret[0]->QRpasswd, cret, 1, '|', sizeof qret[0]->QRpasswd);
981                 extract_token(qret[0]->QRdirname, cret, 2, '|', sizeof qret[0]->QRdirname);
982                 qret[0]->QRflags = extract_int(cret, 3);
983                 qret[0]->QRfloor = extract_int(cret, 4);
984                 qret[0]->QRorder = extract_int(cret, 5);
985                 qret[0]->QRdefaultview = extract_int(cret, 6);
986                 qret[0]->QRflags2 = extract_int(cret, 7);
987         }
988         return ret;
989 }
990
991
992 /* SETR */
993 /* set forget to kick all users out of room */
994 int CtdlIPCSetRoomAttributes(CtdlIPC * ipc, int forget, struct ctdlroom *qret, char *cret)
995 {
996         int ret;
997         char *aaa;
998
999         if (!cret)
1000                 return -2;
1001         if (!qret)
1002                 return -2;
1003
1004         aaa = (char *) malloc(strlen(qret->QRname) + strlen(qret->QRpasswd) + strlen(qret->QRdirname) + 64);
1005         if (!aaa)
1006                 return -1;
1007
1008         sprintf(aaa, "SETR %s|%s|%s|%d|%d|%d|%d|%d|%d",
1009                 qret->QRname, qret->QRpasswd, qret->QRdirname,
1010                 qret->QRflags, forget, qret->QRfloor, qret->QRorder, qret->QRdefaultview, qret->QRflags2);
1011         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1012         free(aaa);
1013         return ret;
1014 }
1015
1016
1017 /* GETA */
1018 int CtdlIPCGetRoomAide(CtdlIPC * ipc, char *cret)
1019 {
1020         if (!cret)
1021                 return -1;
1022
1023         return CtdlIPCGenericCommand(ipc, "GETA", NULL, 0, NULL, NULL, cret);
1024 }
1025
1026
1027 /* SETA */
1028 int CtdlIPCSetRoomAide(CtdlIPC * ipc, const char *username, char *cret)
1029 {
1030         int ret;
1031         char *aaa;
1032
1033         if (!cret)
1034                 return -2;
1035         if (!username)
1036                 return -2;
1037
1038         aaa = (char *) malloc(strlen(username) + 6);
1039         if (!aaa)
1040                 return -1;
1041
1042         sprintf(aaa, "SETA %s", username);
1043         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1044         free(aaa);
1045         return ret;
1046 }
1047
1048
1049 /* ENT0 */
1050 int CtdlIPCPostMessage(CtdlIPC * ipc, int flag, int *subject_required, struct ctdlipcmessage *mr, char *cret)
1051 {
1052         int ret;
1053         char cmd[SIZ];
1054         char *ptr;
1055
1056         if (!cret)
1057                 return -2;
1058         if (!mr)
1059                 return -2;
1060
1061         if (mr->references) {
1062                 for (ptr = mr->references; *ptr != 0; ++ptr) {
1063                         if (*ptr == '|')
1064                                 *ptr = '!';
1065                 }
1066         }
1067
1068         snprintf(cmd, sizeof cmd,
1069                  "ENT0 %d|%s|%d|%d|%s|%s||||||%s|", flag, mr->recipient,
1070                  mr->anonymous, mr->type, mr->subject, mr->author, mr->references);
1071         ret = CtdlIPCGenericCommand(ipc, cmd, mr->text, strlen(mr->text), NULL, NULL, cret);
1072         if ((flag == 0) && (subject_required != NULL)) {
1073                 /* Is the server strongly recommending that the user enter a message subject? */
1074                 if ((cret[3] != '\0') && (cret[4] != '\0')) {
1075                         *subject_required = extract_int(&cret[4], 1);
1076                 }
1077
1078
1079         }
1080         return ret;
1081 }
1082
1083
1084 /* RINF */
1085 int CtdlIPCRoomInfo(CtdlIPC * ipc, char **iret, char *cret)
1086 {
1087         size_t bytes;
1088
1089         if (!cret)
1090                 return -2;
1091         if (!iret)
1092                 return -2;
1093         if (*iret)
1094                 return -2;
1095
1096         return CtdlIPCGenericCommand(ipc, "RINF", NULL, 0, iret, &bytes, cret);
1097 }
1098
1099
1100 /* DELE */
1101 int CtdlIPCDeleteMessage(CtdlIPC * ipc, long msgnum, char *cret)
1102 {
1103         char aaa[64];
1104
1105         if (!cret)
1106                 return -2;
1107         if (!msgnum)
1108                 return -2;
1109
1110         sprintf(aaa, "DELE %ld", msgnum);
1111         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1112 }
1113
1114
1115 /* MOVE */
1116 int CtdlIPCMoveMessage(CtdlIPC * ipc, int copy, long msgnum, const char *destroom, char *cret)
1117 {
1118         int ret;
1119         char *aaa;
1120
1121         if (!cret)
1122                 return -2;
1123         if (!destroom)
1124                 return -2;
1125         if (!msgnum)
1126                 return -2;
1127
1128         aaa = (char *) malloc(strlen(destroom) + 28);
1129         if (!aaa)
1130                 return -1;
1131
1132         sprintf(aaa, "MOVE %ld|%s|%d", msgnum, destroom, copy);
1133         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1134         free(aaa);
1135         return ret;
1136 }
1137
1138
1139 /* KILL */
1140 int CtdlIPCDeleteRoom(CtdlIPC * ipc, int for_real, char *cret)
1141 {
1142         char aaa[64];
1143
1144         if (!cret)
1145                 return -2;
1146
1147         sprintf(aaa, "KILL %d", for_real);
1148         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1149 }
1150
1151
1152 /* CRE8 */
1153 int CtdlIPCCreateRoom(CtdlIPC * ipc, int for_real, const char *roomname, int type, const char *password, int floor, char *cret)
1154 {
1155         int ret;
1156         char *aaa;
1157
1158         if (!cret)
1159                 return -2;
1160         if (!roomname)
1161                 return -2;
1162
1163         if (password) {
1164                 aaa = (char *) malloc(strlen(roomname) + strlen(password) + 40);
1165                 if (!aaa)
1166                         return -1;
1167                 sprintf(aaa, "CRE8 %d|%s|%d|%s|%d", for_real, roomname, type, password, floor);
1168         } else {
1169                 aaa = (char *) malloc(strlen(roomname) + 40);
1170                 if (!aaa)
1171                         return -1;
1172                 sprintf(aaa, "CRE8 %d|%s|%d||%d", for_real, roomname, type, floor);
1173         }
1174         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1175         free(aaa);
1176         return ret;
1177 }
1178
1179
1180 /* FORG */
1181 int CtdlIPCForgetRoom(CtdlIPC * ipc, char *cret)
1182 {
1183         if (!cret)
1184                 return -2;
1185
1186         return CtdlIPCGenericCommand(ipc, "FORG", NULL, 0, NULL, NULL, cret);
1187 }
1188
1189
1190 /* MESG */
1191 int CtdlIPCSystemMessage(CtdlIPC * ipc, const char *message, char **mret, char *cret)
1192 {
1193         int ret;
1194         char *aaa;
1195         size_t bytes;
1196
1197         if (!cret)
1198                 return -2;
1199         if (!mret)
1200                 return -2;
1201         if (*mret)
1202                 return -2;
1203         if (!message)
1204                 return -2;
1205
1206         aaa = (char *) malloc(strlen(message) + 6);
1207         if (!aaa)
1208                 return -1;
1209
1210         sprintf(aaa, "MESG %s", message);
1211         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, mret, &bytes, cret);
1212         free(aaa);
1213         return ret;
1214 }
1215
1216
1217 /* GNUR */
1218 int CtdlIPCNextUnvalidatedUser(CtdlIPC * ipc, char *cret)
1219 {
1220         if (!cret)
1221                 return -2;
1222
1223         return CtdlIPCGenericCommand(ipc, "GNUR", NULL, 0, NULL, NULL, cret);
1224 }
1225
1226
1227 /* GREG */
1228 int CtdlIPCGetUserRegistration(CtdlIPC * ipc, const char *username, char **rret, char *cret)
1229 {
1230         int ret;
1231         char *aaa;
1232         size_t bytes;
1233
1234         if (!cret)
1235                 return -2;
1236         if (!rret)
1237                 return -2;
1238         if (*rret)
1239                 return -2;
1240
1241         if (username)
1242                 aaa = (char *) malloc(strlen(username) + 6);
1243         else
1244                 aaa = (char *) malloc(12);
1245         if (!aaa)
1246                 return -1;
1247
1248         if (username)
1249                 sprintf(aaa, "GREG %s", username);
1250         else
1251                 sprintf(aaa, "GREG _SELF_");
1252         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, rret, &bytes, cret);
1253         free(aaa);
1254         return ret;
1255 }
1256
1257
1258 /* VALI */
1259 int CtdlIPCValidateUser(CtdlIPC * ipc, const char *username, int axlevel, char *cret)
1260 {
1261         int ret;
1262         char *aaa;
1263
1264         if (!cret)
1265                 return -2;
1266         if (!username)
1267                 return -2;
1268         if (axlevel < AxDeleted || axlevel > AxAideU)
1269                 return -2;
1270
1271         aaa = (char *) malloc(strlen(username) + 17);
1272         if (!aaa)
1273                 return -1;
1274
1275         sprintf(aaa, "VALI %s|%d", username, axlevel);
1276         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1277         free(aaa);
1278         return ret;
1279 }
1280
1281
1282 /* EINF */
1283 int CtdlIPCSetRoomInfo(CtdlIPC * ipc, int for_real, const char *info, char *cret)
1284 {
1285         char aaa[64];
1286
1287         if (!cret)
1288                 return -1;
1289         if (!info)
1290                 return -1;
1291
1292         sprintf(aaa, "EINF %d", for_real);
1293         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1294 }
1295
1296
1297 /* LIST */
1298 int CtdlIPCUserListing(CtdlIPC * ipc, char *searchstring, char **listing, char *cret)
1299 {
1300         size_t bytes;
1301         char *cmd;
1302         int ret;
1303
1304         if (!cret)
1305                 return -1;
1306         if (!listing)
1307                 return -1;
1308         if (*listing)
1309                 return -1;
1310         if (!searchstring)
1311                 return -1;
1312
1313         cmd = malloc(strlen(searchstring) + 10);
1314         sprintf(cmd, "LIST %s", searchstring);
1315
1316         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, listing, &bytes, cret);
1317         free(cmd);
1318         return (ret);
1319 }
1320
1321
1322 /* REGI */
1323 int CtdlIPCSetRegistration(CtdlIPC * ipc, const char *info, char *cret)
1324 {
1325         if (!cret)
1326                 return -1;
1327         if (!info)
1328                 return -1;
1329
1330         return CtdlIPCGenericCommand(ipc, "REGI", info, strlen(info), NULL, NULL, cret);
1331 }
1332
1333
1334 /* CHEK */
1335 int CtdlIPCMiscCheck(CtdlIPC * ipc, struct ctdlipcmisc *chek, char *cret)
1336 {
1337         int ret;
1338
1339         if (!cret)
1340                 return -1;
1341         if (!chek)
1342                 return -1;
1343
1344         ret = CtdlIPCGenericCommand(ipc, "CHEK", NULL, 0, NULL, NULL, cret);
1345         if (ret / 100 == 2) {
1346                 chek->newmail = extract_long(cret, 0);
1347                 chek->needregis = extract_int(cret, 1);
1348                 chek->needvalid = extract_int(cret, 2);
1349         }
1350         return ret;
1351 }
1352
1353
1354 /* DELF */
1355 int CtdlIPCDeleteFile(CtdlIPC * ipc, const char *filename, char *cret)
1356 {
1357         int ret;
1358         char *aaa;
1359
1360         if (!cret)
1361                 return -2;
1362         if (!filename)
1363                 return -2;
1364
1365         aaa = (char *) malloc(strlen(filename) + 6);
1366         if (!aaa)
1367                 return -1;
1368
1369         sprintf(aaa, "DELF %s", filename);
1370         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1371         free(aaa);
1372         return ret;
1373 }
1374
1375
1376 /* MOVF */
1377 int CtdlIPCMoveFile(CtdlIPC * ipc, const char *filename, const char *destroom, char *cret)
1378 {
1379         int ret;
1380         char *aaa;
1381
1382         if (!cret)
1383                 return -2;
1384         if (!filename)
1385                 return -2;
1386         if (!destroom)
1387                 return -2;
1388
1389         aaa = (char *) malloc(strlen(filename) + strlen(destroom) + 7);
1390         if (!aaa)
1391                 return -1;
1392
1393         sprintf(aaa, "MOVF %s|%s", filename, destroom);
1394         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1395         free(aaa);
1396         return ret;
1397 }
1398
1399
1400 /* RWHO */
1401 int CtdlIPCOnlineUsers(CtdlIPC * ipc, char **listing, time_t * stamp, char *cret)
1402 {
1403         int ret;
1404         size_t bytes;
1405
1406         if (!cret)
1407                 return -1;
1408         if (!listing)
1409                 return -1;
1410         if (*listing)
1411                 return -1;
1412
1413         *stamp = CtdlIPCServerTime(ipc, cret);
1414         if (!*stamp)
1415                 *stamp = time(NULL);
1416         ret = CtdlIPCGenericCommand(ipc, "RWHO", NULL, 0, listing, &bytes, cret);
1417         return ret;
1418 }
1419
1420
1421 /* OPEN */
1422 int CtdlIPCFileDownload(CtdlIPC * ipc, const char *filename, void **buf, size_t resume, void (*progress_gauge_callback)
1423                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
1424 {
1425         int ret;
1426         size_t bytes;
1427         time_t last_mod;
1428         char mimetype[SIZ];
1429         char *aaa;
1430
1431         if (!cret)
1432                 return -2;
1433         if (!filename)
1434                 return -2;
1435         if (!buf)
1436                 return -2;
1437         if (*buf)
1438                 return -2;
1439         if (ipc->downloading)
1440                 return -2;
1441
1442         aaa = (char *) malloc(strlen(filename) + 6);
1443         if (!aaa)
1444                 return -1;
1445
1446         sprintf(aaa, "OPEN %s", filename);
1447         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1448         free(aaa);
1449         if (ret / 100 == 2) {
1450                 ipc->downloading = 1;
1451                 bytes = extract_long(cret, 0);
1452                 last_mod = extract_int(cret, 1);
1453                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1454
1455                 ret = CtdlIPCReadDownload(ipc, buf, bytes, resume, progress_gauge_callback, cret);
1456                 /*
1457                    ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, resume,
1458                    progress_gauge_callback, cret);
1459                  */
1460
1461                 ret = CtdlIPCEndDownload(ipc, cret);
1462                 if (ret / 100 == 2)
1463                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1464         }
1465         return ret;
1466 }
1467
1468
1469 /* OPNA */
1470 int CtdlIPCAttachmentDownload(CtdlIPC * ipc, long msgnum, const char *part, void **buf, void (*progress_gauge_callback)
1471                                (CtdlIPC *, unsigned long, unsigned long), char *cret)
1472 {
1473         int ret;
1474         size_t bytes;
1475         time_t last_mod;
1476         char filename[SIZ];
1477         char mimetype[SIZ];
1478         char aaa[SIZ];
1479
1480         if (!cret)
1481                 return -2;
1482         if (!buf)
1483                 return -2;
1484         if (*buf)
1485                 return -2;
1486         if (!part)
1487                 return -2;
1488         if (!msgnum)
1489                 return -2;
1490         if (ipc->downloading)
1491                 return -2;
1492
1493         sprintf(aaa, "OPNA %ld|%s", msgnum, part);
1494         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1495         if (ret / 100 == 2) {
1496                 ipc->downloading = 1;
1497                 bytes = extract_long(cret, 0);
1498                 last_mod = extract_int(cret, 1);
1499                 extract_token(filename, cret, 2, '|', sizeof filename);
1500                 extract_token(mimetype, cret, 3, '|', sizeof mimetype);
1501                 /* ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1502                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1503                 ret = CtdlIPCEndDownload(ipc, cret);
1504                 if (ret / 100 == 2)
1505                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1506         }
1507         return ret;
1508 }
1509
1510
1511 /* OIMG */
1512 int CtdlIPCImageDownload(CtdlIPC * ipc, const char *filename, void **buf, void (*progress_gauge_callback)
1513                           (CtdlIPC *, unsigned long, unsigned long), char *cret)
1514 {
1515         int ret;
1516         size_t bytes;
1517         time_t last_mod;
1518         char mimetype[SIZ];
1519         char *aaa;
1520
1521         if (!cret)
1522                 return -1;
1523         if (!buf)
1524                 return -1;
1525         if (*buf)
1526                 return -1;
1527         if (!filename)
1528                 return -1;
1529         if (ipc->downloading)
1530                 return -1;
1531
1532         aaa = (char *) malloc(strlen(filename) + 6);
1533         if (!aaa)
1534                 return -1;
1535
1536         sprintf(aaa, "OIMG %s", filename);
1537         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1538         free(aaa);
1539         if (ret / 100 == 2) {
1540                 ipc->downloading = 1;
1541                 bytes = extract_long(cret, 0);
1542                 last_mod = extract_int(cret, 1);
1543                 extract_token(mimetype, cret, 2, '|', sizeof mimetype);
1544 /*              ret = CtdlIPCReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret); */
1545                 ret = CtdlIPCHighSpeedReadDownload(ipc, buf, bytes, 0, progress_gauge_callback, cret);
1546                 ret = CtdlIPCEndDownload(ipc, cret);
1547                 if (ret / 100 == 2)
1548                         sprintf(cret, "%d|%ld|%s|%s", (int) bytes, last_mod, filename, mimetype);
1549         }
1550         return ret;
1551 }
1552
1553
1554 /* UOPN */
1555 int CtdlIPCFileUpload(CtdlIPC * ipc, const char *save_as, const char *comment, const char *path, void (*progress_gauge_callback)
1556                        (CtdlIPC *, unsigned long, unsigned long), char *cret)
1557 {
1558         int ret;
1559         char *aaa;
1560         FILE *uploadFP;
1561         char MimeTestBuf[64];
1562         const char *MimeType;
1563         long len;
1564
1565         if (!cret)
1566                 return -1;
1567         if (!save_as)
1568                 return -1;
1569         if (!comment)
1570                 return -1;
1571         if (!path)
1572                 return -1;
1573         if (!*path)
1574                 return -1;
1575         if (ipc->uploading)
1576                 return -1;
1577
1578         uploadFP = fopen(path, "r");
1579         if (!uploadFP)
1580                 return -2;
1581
1582         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1583         rewind(uploadFP);
1584         if (len < 0)
1585                 return -3;
1586
1587         MimeType = GuessMimeType(&MimeTestBuf[0], len);
1588         aaa = (char *) malloc(strlen(save_as) + strlen(MimeType) + strlen(comment) + 7);
1589         if (!aaa)
1590                 return -1;
1591
1592         sprintf(aaa, "UOPN %s|%s|%s", save_as, MimeType, comment);
1593         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1594         free(aaa);
1595         if (ret / 100 == 2) {
1596                 ipc->uploading = 1;
1597                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1598                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1599                 ipc->uploading = 0;
1600         }
1601         return ret;
1602 }
1603
1604
1605 /* UIMG */
1606 int CtdlIPCImageUpload(CtdlIPC * ipc, int for_real, const char *path, const char *save_as, void (*progress_gauge_callback)
1607                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
1608 {
1609         int ret;
1610         FILE *uploadFP;
1611         char *aaa;
1612         char MimeTestBuf[64];
1613         const char *MimeType;
1614         long len;
1615
1616         if (!cret)
1617                 return -1;
1618         if (!save_as)
1619                 return -1;
1620         if (!path && for_real)
1621                 return -1;
1622         if (!*path && for_real)
1623                 return -1;
1624         if (ipc->uploading)
1625                 return -1;
1626
1627         aaa = (char *) malloc(strlen(save_as) + 17);
1628         if (!aaa)
1629                 return -1;
1630
1631         uploadFP = fopen(path, "r");
1632         if (!uploadFP)
1633                 return -2;
1634
1635         len = fread(&MimeTestBuf[0], 1, 64, uploadFP);
1636         rewind(uploadFP);
1637         if (len < 0)
1638                 return -3;
1639         MimeType = GuessMimeType(&MimeTestBuf[0], 64);
1640
1641         sprintf(aaa, "UIMG %d|%s|%s", for_real, MimeType, save_as);
1642         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1643         free(aaa);
1644         if (ret / 100 == 2 && for_real) {
1645                 ipc->uploading = 1;
1646                 ret = CtdlIPCWriteUpload(ipc, uploadFP, progress_gauge_callback, cret);
1647                 ret = CtdlIPCEndUpload(ipc, (ret == -2 ? 1 : 0), cret);
1648                 ipc->uploading = 0;
1649         }
1650         return ret;
1651 }
1652
1653
1654 /* QUSR */
1655 int CtdlIPCQueryUsername(CtdlIPC * ipc, const char *username, char *cret)
1656 {
1657         int ret;
1658         char *aaa;
1659
1660         if (!cret)
1661                 return -2;
1662         if (!username)
1663                 return -2;
1664
1665         aaa = (char *) malloc(strlen(username) + 6);
1666         if (!aaa)
1667                 return -1;
1668
1669         sprintf(aaa, "QUSR %s", username);
1670         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1671         free(aaa);
1672         return ret;
1673 }
1674
1675
1676 /* LFLR */
1677 int CtdlIPCFloorListing(CtdlIPC * ipc, char **listing, char *cret)
1678 {
1679         size_t bytes;
1680
1681         if (!cret)
1682                 return -2;
1683         if (!listing)
1684                 return -2;
1685         if (*listing)
1686                 return -2;
1687
1688         return CtdlIPCGenericCommand(ipc, "LFLR", NULL, 0, listing, &bytes, cret);
1689 }
1690
1691
1692 /* CFLR */
1693 int CtdlIPCCreateFloor(CtdlIPC * ipc, int for_real, const char *name, char *cret)
1694 {
1695         int ret;
1696         char aaa[SIZ];
1697
1698         if (!cret)
1699                 return -2;
1700         if (!name)
1701                 return -2;
1702
1703         sprintf(aaa, "CFLR %s|%d", name, for_real);
1704         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1705         return ret;
1706 }
1707
1708
1709 /* KFLR */
1710 int CtdlIPCDeleteFloor(CtdlIPC * ipc, int for_real, int floornum, char *cret)
1711 {
1712         char aaa[SIZ];
1713
1714         if (!cret)
1715                 return -1;
1716         if (floornum < 0)
1717                 return -1;
1718
1719         sprintf(aaa, "KFLR %d|%d", floornum, for_real);
1720         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1721 }
1722
1723
1724 /* EFLR */
1725 int CtdlIPCEditFloor(CtdlIPC * ipc, int floornum, const char *floorname, char *cret)
1726 {
1727         int ret;
1728         char aaa[SIZ];
1729
1730         if (!cret)
1731                 return -2;
1732         if (!floorname)
1733                 return -2;
1734         if (floornum < 0)
1735                 return -2;
1736
1737         sprintf(aaa, "EFLR %d|%s", floornum, floorname);
1738         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1739         return ret;
1740 }
1741
1742
1743 /*
1744  * IDEN 
1745  *
1746  * You only need to fill out hostname, the defaults will be used if any of the
1747  * other fields are not set properly.
1748  */
1749 int CtdlIPCIdentifySoftware(CtdlIPC * ipc, int developerid, int clientid,
1750                             int revision, const char *software_name, const char *hostname, char *cret)
1751 {
1752         int ret;
1753         char *aaa;
1754
1755         if (developerid < 0 || clientid < 0 || revision < 0 || !software_name) {
1756                 developerid = 8;
1757                 clientid = 0;
1758                 revision = CLIENT_VERSION - 600;
1759                 software_name = "Citadel (libcitadel)";
1760         }
1761         if (!hostname)
1762                 return -2;
1763
1764         aaa = (char *) malloc(strlen(software_name) + strlen(hostname) + 29);
1765         if (!aaa)
1766                 return -1;
1767
1768         sprintf(aaa, "IDEN %d|%d|%d|%s|%s", developerid, clientid, revision, software_name, hostname);
1769         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1770         free(aaa);
1771         return ret;
1772 }
1773
1774
1775 /* SEXP */
1776 int CtdlIPCSendInstantMessage(CtdlIPC * ipc, const char *username, const char *text, char *cret)
1777 {
1778         int ret;
1779         char *aaa;
1780
1781         if (!cret)
1782                 return -2;
1783         if (!username)
1784                 return -2;
1785
1786         aaa = (char *) malloc(strlen(username) + 8);
1787         if (!aaa)
1788                 return -1;
1789
1790         if (text) {
1791                 sprintf(aaa, "SEXP %s|-", username);
1792                 ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1793         } else {
1794                 sprintf(aaa, "SEXP %s||", username);
1795                 ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1796         }
1797         free(aaa);
1798         return ret;
1799 }
1800
1801
1802 /* GEXP */
1803 int CtdlIPCGetInstantMessage(CtdlIPC * ipc, char **listing, char *cret)
1804 {
1805         size_t bytes;
1806
1807         if (!cret)
1808                 return -2;
1809         if (!listing)
1810                 return -2;
1811         if (*listing)
1812                 return -2;
1813
1814         return CtdlIPCGenericCommand(ipc, "GEXP", NULL, 0, listing, &bytes, cret);
1815 }
1816
1817
1818 /* DEXP */
1819 /* mode is 0 = enable, 1 = disable, 2 = status */
1820 int CtdlIPCEnableInstantMessageReceipt(CtdlIPC * ipc, int mode, char *cret)
1821 {
1822         char aaa[64];
1823
1824         if (!cret)
1825                 return -2;
1826
1827         sprintf(aaa, "DEXP %d", mode);
1828         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1829 }
1830
1831
1832 /* EBIO */
1833 int CtdlIPCSetBio(CtdlIPC * ipc, char *bio, char *cret)
1834 {
1835         if (!cret)
1836                 return -2;
1837         if (!bio)
1838                 return -2;
1839
1840         return CtdlIPCGenericCommand(ipc, "EBIO", bio, strlen(bio), NULL, NULL, cret);
1841 }
1842
1843
1844 /* RBIO */
1845 int CtdlIPCGetBio(CtdlIPC * ipc, const char *username, char **listing, char *cret)
1846 {
1847         int ret;
1848         size_t bytes;
1849         char *aaa;
1850
1851         if (!cret)
1852                 return -2;
1853         if (!username)
1854                 return -2;
1855         if (!listing)
1856                 return -2;
1857         if (*listing)
1858                 return -2;
1859
1860         aaa = (char *) malloc(strlen(username) + 6);
1861         if (!aaa)
1862                 return -1;
1863
1864         sprintf(aaa, "RBIO %s", username);
1865         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
1866         free(aaa);
1867         return ret;
1868 }
1869
1870
1871 /* LBIO */
1872 int CtdlIPCListUsersWithBios(CtdlIPC * ipc, char **listing, char *cret)
1873 {
1874         size_t bytes;
1875
1876         if (!cret)
1877                 return -2;
1878         if (!listing)
1879                 return -2;
1880         if (*listing)
1881                 return -2;
1882
1883         return CtdlIPCGenericCommand(ipc, "LBIO", NULL, 0, listing, &bytes, cret);
1884 }
1885
1886
1887 /* STEL */
1888 int CtdlIPCStealthMode(CtdlIPC * ipc, int mode, char *cret)
1889 {
1890         char aaa[64];
1891
1892         if (!cret)
1893                 return -1;
1894
1895         sprintf(aaa, "STEL %d", mode ? 1 : 0);
1896         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1897 }
1898
1899
1900 /* TERM */
1901 int CtdlIPCTerminateSession(CtdlIPC * ipc, int sid, char *cret)
1902 {
1903         char aaa[64];
1904
1905         if (!cret)
1906                 return -1;
1907
1908         sprintf(aaa, "TERM %d", sid);
1909         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1910 }
1911
1912
1913 /* DOWN */
1914 int CtdlIPCTerminateServerNow(CtdlIPC * ipc, char *cret)
1915 {
1916         if (!cret)
1917                 return -1;
1918
1919         return CtdlIPCGenericCommand(ipc, "DOWN", NULL, 0, NULL, NULL, cret);
1920 }
1921
1922
1923 /* SCDN */
1924 int CtdlIPCTerminateServerScheduled(CtdlIPC * ipc, int mode, char *cret)
1925 {
1926         char aaa[16];
1927
1928         if (!cret)
1929                 return -1;
1930
1931         sprintf(aaa, "SCDN %d", mode ? 1 : 0);
1932         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1933 }
1934
1935
1936 /* EMSG */
1937 int CtdlIPCEnterSystemMessage(CtdlIPC * ipc, const char *filename, const char *text, char *cret)
1938 {
1939         int ret;
1940         char *aaa;
1941
1942         if (!cret)
1943                 return -2;
1944         if (!text)
1945                 return -2;
1946         if (!filename)
1947                 return -2;
1948
1949         aaa = (char *) malloc(strlen(filename) + 6);
1950         if (!aaa)
1951                 return -1;
1952
1953         sprintf(aaa, "EMSG %s", filename);
1954         ret = CtdlIPCGenericCommand(ipc, aaa, text, strlen(text), NULL, NULL, cret);
1955         free(aaa);
1956         return ret;
1957 }
1958
1959
1960 /* HCHG */
1961 int CtdlIPCChangeHostname(CtdlIPC * ipc, const char *hostname, char *cret)
1962 {
1963         int ret;
1964         char *aaa;
1965
1966         if (!cret)
1967                 return -2;
1968         if (!hostname)
1969                 return -2;
1970
1971         aaa = (char *) malloc(strlen(hostname) + 6);
1972         if (!aaa)
1973                 return -1;
1974
1975         sprintf(aaa, "HCHG %s", hostname);
1976         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1977         free(aaa);
1978         return ret;
1979 }
1980
1981
1982 /* RCHG */
1983 int CtdlIPCChangeRoomname(CtdlIPC * ipc, const char *roomname, char *cret)
1984 {
1985         int ret;
1986         char *aaa;
1987
1988         if (!cret)
1989                 return -2;
1990         if (!roomname)
1991                 return -2;
1992
1993         aaa = (char *) malloc(strlen(roomname) + 6);
1994         if (!aaa)
1995                 return -1;
1996
1997         sprintf(aaa, "RCHG %s", roomname);
1998         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
1999         free(aaa);
2000         return ret;
2001 }
2002
2003
2004 /* UCHG */
2005 int CtdlIPCChangeUsername(CtdlIPC * ipc, const char *username, char *cret)
2006 {
2007         int ret;
2008         char *aaa;
2009
2010         if (!cret)
2011                 return -2;
2012         if (!username)
2013                 return -2;
2014
2015         aaa = (char *) malloc(strlen(username) + 6);
2016         if (!aaa)
2017                 return -1;
2018
2019         sprintf(aaa, "UCHG %s", username);
2020         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2021         free(aaa);
2022         return ret;
2023 }
2024
2025
2026 /* TIME */
2027 /* This function returns the actual server time reported, or 0 if error */
2028 time_t CtdlIPCServerTime(CtdlIPC * ipc, char *cret)
2029 {
2030         time_t tret;
2031         int ret;
2032
2033         ret = CtdlIPCGenericCommand(ipc, "TIME", NULL, 0, NULL, NULL, cret);
2034         if (ret / 100 == 2) {
2035                 tret = extract_long(cret, 0);
2036         } else {
2037                 tret = 0L;
2038         }
2039         return tret;
2040 }
2041
2042
2043 /* AGUP */
2044 int CtdlIPCAideGetUserParameters(CtdlIPC * ipc, const char *who, struct ctdluser **uret, char *cret)
2045 {
2046         int ret;
2047         char aaa[SIZ];
2048
2049         if (!cret)
2050                 return -2;
2051         if (!uret)
2052                 return -2;
2053         if (!*uret)
2054                 *uret = (struct ctdluser *) calloc(1, sizeof(struct ctdluser));
2055         if (!*uret)
2056                 return -1;
2057
2058         sprintf(aaa, "AGUP %s", who);
2059         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2060
2061         if (ret / 100 == 2) {
2062                 extract_token(uret[0]->fullname, cret, 0, '|', sizeof uret[0]->fullname);
2063                 extract_token(uret[0]->password, cret, 1, '|', sizeof uret[0]->password);
2064                 uret[0]->flags = extract_int(cret, 2);
2065                 uret[0]->timescalled = extract_long(cret, 3);
2066                 uret[0]->posted = extract_long(cret, 4);
2067                 uret[0]->axlevel = extract_int(cret, 5);
2068                 uret[0]->usernum = extract_long(cret, 6);
2069                 uret[0]->lastcall = extract_long(cret, 7);
2070                 uret[0]->USuserpurge = extract_int(cret, 8);
2071         }
2072         return ret;
2073 }
2074
2075
2076 /* ASUP */
2077 int CtdlIPCAideSetUserParameters(CtdlIPC * ipc, const struct ctdluser *uret, char *cret)
2078 {
2079         int ret;
2080         char *aaa;
2081
2082         if (!cret)
2083                 return -2;
2084         if (!uret)
2085                 return -2;
2086
2087         aaa = (char *) malloc(strlen(uret->fullname) + strlen(uret->password) + 84);
2088         if (!aaa)
2089                 return -1;
2090
2091         sprintf(aaa, "ASUP %s|%s|%d|%ld|%ld|%d|%ld|%ld|%d",
2092                 uret->fullname, uret->password, uret->flags, uret->timescalled,
2093                 uret->posted, uret->axlevel, uret->usernum, uret->lastcall, uret->USuserpurge);
2094         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2095         free(aaa);
2096         return ret;
2097 }
2098
2099
2100 /* AGEA */
2101 int CtdlIPCAideGetEmailAddresses(CtdlIPC * ipc, const char *who, char *target_buf, char *cret)
2102 {
2103         int ret;
2104         char aaa[SIZ];
2105         char *emailaddrs = NULL;
2106         size_t emailaddrs_len = 0;
2107
2108         sprintf(aaa, "AGEA %s", who);
2109         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, &emailaddrs, &emailaddrs_len, cret);
2110
2111         if (ret / 100 == 1) {
2112                 strcpy(target_buf, emailaddrs);
2113         }
2114
2115         if (emailaddrs != NULL) {
2116                 free(emailaddrs);
2117         }
2118
2119         return ret;
2120 }
2121
2122
2123 /* ASEA */
2124 int CtdlIPCAideSetEmailAddresses(CtdlIPC * ipc, const char *who, char *emailaddrs, char *cret)
2125 {
2126         char aaa[SIZ];
2127         int ret;
2128
2129         if (!who)
2130                 return -2;
2131         if (!emailaddrs)
2132                 return -2;
2133         if (!cret)
2134                 return -2;
2135
2136         sprintf(aaa, "ASEA %s", who);
2137         ret = CtdlIPCGenericCommand(ipc, aaa, emailaddrs, 0, NULL, NULL, cret);
2138         return ret;
2139 }
2140
2141
2142 /* GPEX */
2143 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2144 /* caller must free the struct ExpirePolicy */
2145 int CtdlIPCGetMessageExpirationPolicy(CtdlIPC * ipc, GPEXWhichPolicy which, struct ExpirePolicy **policy, char *cret)
2146 {
2147         static char *proto[] = {
2148                 strof(roompolicy),
2149                 strof(floorpolicy),
2150                 strof(sitepolicy),
2151                 strof(mailboxespolicy)
2152         };
2153         char cmd[256];
2154         int ret;
2155
2156         if (!cret)
2157                 return -2;
2158         if (!policy)
2159                 return -2;
2160         if (!*policy)
2161                 *policy = (struct ExpirePolicy *) calloc(1, sizeof(struct ExpirePolicy));
2162         if (!*policy)
2163                 return -1;
2164         if (which < 0 || which > 3)
2165                 return -2;
2166
2167         sprintf(cmd, "GPEX %s", proto[which]);
2168         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2169         if (ret / 100 == 2) {
2170                 policy[0]->expire_mode = extract_int(cret, 0);
2171                 policy[0]->expire_value = extract_int(cret, 1);
2172         }
2173         return ret;
2174 }
2175
2176
2177 /* SPEX */
2178 /* which is 0 = room, 1 = floor, 2 = site, 3 = default for mailboxes */
2179 /* policy is 0 = inherit, 1 = no purge, 2 = by count, 3 = by age (days) */
2180 int CtdlIPCSetMessageExpirationPolicy(CtdlIPC * ipc, int which, struct ExpirePolicy *policy, char *cret)
2181 {
2182         char aaa[38];
2183         char *whichvals[] = { "room", "floor", "site", "mailboxes" };
2184
2185         if (!cret)
2186                 return -2;
2187         if (which < 0 || which > 3)
2188                 return -2;
2189         if (!policy)
2190                 return -2;
2191         if (policy->expire_mode < 0 || policy->expire_mode > 3)
2192                 return -2;
2193         if (policy->expire_mode >= 2 && policy->expire_value < 1)
2194                 return -2;
2195
2196         sprintf(aaa, "SPEX %s|%d|%d", whichvals[which], policy->expire_mode, policy->expire_value);
2197         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2198 }
2199
2200
2201 /* CONF GET */
2202 int CtdlIPCGetSystemConfig(CtdlIPC * ipc, char **listing, char *cret)
2203 {
2204         size_t bytes;
2205
2206         if (!cret)
2207                 return -2;
2208         if (!listing)
2209                 return -2;
2210         if (*listing)
2211                 return -2;
2212
2213         return CtdlIPCGenericCommand(ipc, "CONF GET", NULL, 0, listing, &bytes, cret);
2214 }
2215
2216
2217 /* CONF SET */
2218 int CtdlIPCSetSystemConfig(CtdlIPC * ipc, const char *listing, char *cret)
2219 {
2220         if (!cret)
2221                 return -2;
2222         if (!listing)
2223                 return -2;
2224
2225         return CtdlIPCGenericCommand(ipc, "CONF SET", listing, strlen(listing), NULL, NULL, cret);
2226 }
2227
2228
2229 /* CONF GETSYS */
2230 int CtdlIPCGetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, char **listing, char *cret)
2231 {
2232         int ret;
2233         char *aaa;
2234         size_t bytes;
2235
2236         if (!cret)
2237                 return -2;
2238         if (!mimetype)
2239                 return -2;
2240         if (!listing)
2241                 return -2;
2242         if (*listing)
2243                 return -2;
2244
2245         aaa = malloc(strlen(mimetype) + 13);
2246         if (!aaa)
2247                 return -1;
2248         sprintf(aaa, "CONF GETSYS|%s", mimetype);
2249         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, listing, &bytes, cret);
2250         free(aaa);
2251         return ret;
2252 }
2253
2254
2255 /* CONF PUTSYS */
2256 int CtdlIPCSetSystemConfigByType(CtdlIPC * ipc, const char *mimetype, const char *listing, char *cret)
2257 {
2258         int ret;
2259         char *aaa;
2260
2261         if (!cret)
2262                 return -2;
2263         if (!mimetype)
2264                 return -2;
2265         if (!listing)
2266                 return -2;
2267
2268         aaa = malloc(strlen(mimetype) + 13);
2269         if (!aaa)
2270                 return -1;
2271         sprintf(aaa, "CONF PUTSYS|%s", mimetype);
2272         ret = CtdlIPCGenericCommand(ipc, aaa, listing, strlen(listing), NULL, NULL, cret);
2273         free(aaa);
2274         return ret;
2275 }
2276
2277
2278 /* GNET */
2279 int CtdlIPCGetRoomNetworkConfig(CtdlIPC * ipc, char **listing, char *cret)
2280 {
2281         size_t bytes;
2282
2283         if (!cret)
2284                 return -2;
2285         if (!listing)
2286                 return -2;
2287         if (*listing)
2288                 return -2;
2289
2290         return CtdlIPCGenericCommand(ipc, "GNET", NULL, 0, listing, &bytes, cret);
2291 }
2292
2293
2294 /* SNET */
2295 int CtdlIPCSetRoomNetworkConfig(CtdlIPC * ipc, const char *listing, char *cret)
2296 {
2297         if (!cret)
2298                 return -2;
2299         if (!listing)
2300                 return -2;
2301
2302         return CtdlIPCGenericCommand(ipc, "SNET", listing, strlen(listing), NULL, NULL, cret);
2303 }
2304
2305
2306 /* REQT */
2307 int CtdlIPCRequestClientLogout(CtdlIPC * ipc, int session, char *cret)
2308 {
2309         char aaa[64];
2310
2311         if (!cret)
2312                 return -2;
2313         if (session < 0)
2314                 return -2;
2315
2316         sprintf(aaa, "REQT %d", session);
2317         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2318 }
2319
2320
2321 /* SEEN */
2322 int CtdlIPCSetMessageSeen(CtdlIPC * ipc, long msgnum, int seen, char *cret)
2323 {
2324         char aaa[27];
2325
2326         if (!cret)
2327                 return -2;
2328         if (msgnum < 0)
2329                 return -2;
2330
2331         sprintf(aaa, "SEEN %ld|%d", msgnum, seen ? 1 : 0);
2332         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2333 }
2334
2335
2336 /* STLS */
2337 int CtdlIPCStartEncryption(CtdlIPC * ipc, char *cret)
2338 {
2339         int a;
2340         int r;
2341         char buf[SIZ];
2342
2343 #ifdef HAVE_OPENSSL
2344         SSL *temp_ssl;
2345
2346         /* New SSL object */
2347         temp_ssl = SSL_new(ssl_ctx);
2348         if (!temp_ssl) {
2349                 error_printf("SSL_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2350                 return -2;
2351         }
2352         /* Pointless flag waving */
2353 #if SSLEAY_VERSION_NUMBER >= 0x0922
2354         SSL_set_session_id_context(temp_ssl, (const unsigned char *) "Citadel SID", 14);
2355 #endif
2356
2357         /* Associate network connection with SSL object */
2358         if (SSL_set_fd(temp_ssl, ipc->sock) < 1) {
2359                 error_printf("SSL_set_fd failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2360                 return -2;
2361         }
2362
2363         if (status_hook != NULL) {
2364                 status_hook("Requesting encryption...\r");
2365         }
2366
2367         /* Ready to start SSL/TLS */
2368         r = CtdlIPCGenericCommand(ipc, "STLS", NULL, 0, NULL, NULL, cret);
2369         if (r / 100 != 2) {
2370                 error_printf("Server can't start TLS: %s\n", buf);
2371                 endtls(temp_ssl);
2372                 return r;
2373         }
2374
2375         /* Do SSL/TLS handshake */
2376         if ((a = SSL_connect(temp_ssl)) < 1) {
2377                 error_printf("SSL_connect failed: %s\n", ERR_reason_error_string(ERR_get_error()));
2378                 endtls(temp_ssl);
2379                 return -2;
2380         }
2381         ipc->ssl = temp_ssl;
2382
2383         error_printf("Encrypting with %s cipher %s\n",
2384                      SSL_CIPHER_get_version(SSL_get_current_cipher(ipc->ssl)), SSL_CIPHER_get_name(SSL_get_current_cipher(ipc->ssl))
2385             );
2386         return r;
2387 #else
2388         return 0;
2389 #endif                          /* HAVE_OPENSSL */
2390 }
2391
2392
2393 #ifdef HAVE_OPENSSL
2394 static void endtls(SSL * ssl)
2395 {
2396         if (ssl) {
2397                 SSL_shutdown(ssl);
2398                 SSL_free(ssl);
2399         }
2400 }
2401 #endif
2402
2403
2404 /* QDIR */
2405 int CtdlIPCDirectoryLookup(CtdlIPC * ipc, const char *address, char *cret)
2406 {
2407         int ret;
2408         char *aaa;
2409
2410         if (!address)
2411                 return -2;
2412         if (!cret)
2413                 return -2;
2414
2415         aaa = (char *) malloc(strlen(address) + 6);
2416         if (!aaa)
2417                 return -1;
2418
2419         sprintf(aaa, "QDIR %s", address);
2420         ret = CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2421         free(aaa);
2422         return ret;
2423 }
2424
2425
2426 /* IPGM */
2427 int CtdlIPCInternalProgram(CtdlIPC * ipc, int secret, char *cret)
2428 {
2429         char aaa[30];
2430
2431         if (!cret)
2432                 return -2;
2433         sprintf(aaa, "IPGM %d", secret);
2434         return CtdlIPCGenericCommand(ipc, aaa, NULL, 0, NULL, NULL, cret);
2435 }
2436
2437
2438
2439
2440 /* ************************************************************************** */
2441 /*           Stuff below this line is not for public consumption            */
2442 /* ************************************************************************** */
2443
2444
2445 /* Read a listing from the server up to 000.  Append to dest if it exists */
2446 char *CtdlIPCReadListing(CtdlIPC * ipc, char *dest)
2447 {
2448         size_t length = 0;
2449         size_t linelength;
2450         char *ret = NULL;
2451         char aaa[SIZ];
2452
2453         ret = dest;
2454         if (ret != NULL) {
2455                 length = strlen(ret);
2456         } else {
2457                 length = 0;
2458         }
2459
2460         while (CtdlIPC_getline(ipc, aaa), strcmp(aaa, "000")) {
2461                 linelength = strlen(aaa);
2462                 ret = (char *) realloc(ret, (size_t) (length + linelength + 2));
2463                 if (ret) {
2464                         strcpy(&ret[length], aaa);
2465                         length += linelength;
2466                         strcpy(&ret[length++], "\n");
2467                 }
2468         }
2469
2470         return (ret);
2471 }
2472
2473
2474 /* Send a listing to the server; generate the ending 000. */
2475 int CtdlIPCSendListing(CtdlIPC * ipc, const char *listing)
2476 {
2477         char *text;
2478
2479         text = (char *) malloc(strlen(listing) + 6);
2480         if (text) {
2481                 strcpy(text, listing);
2482                 while (text[strlen(text) - 1] == '\n')
2483                         text[strlen(text) - 1] = '\0';
2484                 strcat(text, "\n000");
2485                 CtdlIPC_putline(ipc, text);
2486                 free(text);
2487                 text = NULL;
2488         } else {
2489                 /* Malloc failed but we are committed to send */
2490                 /* This may result in extra blanks at the bottom */
2491                 CtdlIPC_putline(ipc, text);
2492                 CtdlIPC_putline(ipc, "000");
2493         }
2494         return 0;
2495 }
2496
2497
2498 /* Partial read of file from server */
2499 size_t CtdlIPCPartialRead(CtdlIPC * ipc, void **buf, size_t offset, size_t bytes, char *cret)
2500 {
2501         size_t len = 0;
2502         char aaa[SIZ];
2503
2504         if (!buf)
2505                 return 0;
2506         if (!cret)
2507                 return 0;
2508         if (bytes < 1)
2509                 return 0;
2510
2511         CtdlIPC_lock(ipc);
2512         sprintf(aaa, "READ %d|%d", (int) offset, (int) bytes);
2513         CtdlIPC_putline(ipc, aaa);
2514         CtdlIPC_getline(ipc, aaa);
2515         if (aaa[0] != '6')
2516                 strcpy(cret, &aaa[4]);
2517         else {
2518                 len = extract_long(&aaa[4], 0);
2519                 *buf = (void *) realloc(*buf, (size_t) (offset + len));
2520                 if (*buf) {
2521                         /* I know what I'm doing */
2522                         serv_read(ipc, ((char *) (*buf) + offset), len);
2523                 } else {
2524                         /* We have to read regardless */
2525                         serv_read(ipc, aaa, len);
2526                         len = 0;
2527                 }
2528         }
2529         CtdlIPC_unlock(ipc);
2530         return len;
2531 }
2532
2533
2534 /* CLOS */
2535 int CtdlIPCEndDownload(CtdlIPC * ipc, char *cret)
2536 {
2537         int ret;
2538
2539         if (!cret)
2540                 return -2;
2541         if (!ipc->downloading)
2542                 return -2;
2543
2544         ret = CtdlIPCGenericCommand(ipc, "CLOS", NULL, 0, NULL, NULL, cret);
2545         if (ret / 100 == 2)
2546                 ipc->downloading = 0;
2547         return ret;
2548 }
2549
2550
2551 /* MSGP */
2552 int CtdlIPCSpecifyPreferredFormats(CtdlIPC * ipc, char *cret, char *formats)
2553 {
2554         int ret;
2555         char cmd[SIZ];
2556
2557         snprintf(cmd, sizeof cmd, "MSGP %s", formats);
2558         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2559         return ret;
2560 }
2561
2562
2563
2564 /* READ */
2565 int CtdlIPCReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2566                          (CtdlIPC *, unsigned long, unsigned long), char *cret)
2567 {
2568         size_t len;
2569
2570         if (!cret)
2571                 return -1;
2572         if (!buf)
2573                 return -1;
2574         if (*buf)
2575                 return -1;
2576         if (!ipc->downloading)
2577                 return -1;
2578
2579         len = resume;
2580         if (progress_gauge_callback)
2581                 progress_gauge_callback(ipc, len, bytes);
2582         while (len < bytes) {
2583                 size_t block;
2584
2585                 block = CtdlIPCPartialRead(ipc, buf, len, 4096, cret);
2586                 if (block == 0) {
2587                         free(*buf);
2588                         return 0;
2589                 }
2590                 len += block;
2591                 if (progress_gauge_callback)
2592                         progress_gauge_callback(ipc, len, bytes);
2593         }
2594         return len;
2595 }
2596
2597 /* READ - pipelined */
2598 int CtdlIPCHighSpeedReadDownload(CtdlIPC * ipc, void **buf, size_t bytes, size_t resume, void (*progress_gauge_callback)
2599                                   (CtdlIPC *, unsigned long, unsigned long), char *cret)
2600 {
2601         size_t len;
2602         int calls;              /* How many calls in the pipeline */
2603         int i;                  /* iterator */
2604         char aaa[4096];
2605
2606         if (!cret)
2607                 return -1;
2608         if (!buf)
2609                 return -1;
2610         if (*buf)
2611                 return -1;
2612         if (!ipc->downloading)
2613                 return -1;
2614
2615         *buf = (void *) realloc(*buf, bytes - resume);
2616         if (!*buf)
2617                 return -1;
2618
2619         len = 0;
2620         CtdlIPC_lock(ipc);
2621         if (progress_gauge_callback)
2622                 progress_gauge_callback(ipc, len, bytes);
2623
2624         /* How many calls will be in the pipeline? */
2625         calls = (bytes - resume) / 4096;
2626         if ((bytes - resume) % 4096)
2627                 calls++;
2628
2629         /* Send all requests at once */
2630         for (i = 0; i < calls; i++) {
2631                 sprintf(aaa, "READ %d|4096", (int) (i * 4096 + resume));
2632                 CtdlIPC_putline(ipc, aaa);
2633         }
2634
2635         /* Receive all responses at once */
2636         for (i = 0; i < calls; i++) {
2637                 CtdlIPC_getline(ipc, aaa);
2638                 if (aaa[0] != '6')
2639                         strcpy(cret, &aaa[4]);
2640                 else {
2641                         len = extract_long(&aaa[4], 0);
2642                         /* I know what I'm doing */
2643                         serv_read(ipc, ((char *) (*buf) + (i * 4096)), len);
2644                 }
2645                 if (progress_gauge_callback)
2646                         progress_gauge_callback(ipc, i * 4096 + len, bytes);
2647         }
2648         CtdlIPC_unlock(ipc);
2649         return len;
2650 }
2651
2652
2653 /* UCLS */
2654 int CtdlIPCEndUpload(CtdlIPC * ipc, int discard, char *cret)
2655 {
2656         int ret;
2657         char cmd[8];
2658
2659         if (!cret)
2660                 return -1;
2661         if (!ipc->uploading)
2662                 return -1;
2663
2664         sprintf(cmd, "UCLS %d", discard ? 0 : 1);
2665         ret = CtdlIPCGenericCommand(ipc, cmd, NULL, 0, NULL, NULL, cret);
2666         ipc->uploading = 0;
2667         return ret;
2668 }
2669
2670
2671 /* WRIT */
2672 int CtdlIPCWriteUpload(CtdlIPC * ipc, FILE * uploadFP, void (*progress_gauge_callback)
2673                         (CtdlIPC *, unsigned long, unsigned long), char *cret)
2674 {
2675         int ret = -1;
2676         size_t offset = 0;
2677         size_t bytes;
2678         char aaa[SIZ];
2679         char buf[4096];
2680         FILE *fd = uploadFP;
2681         int ferr;
2682
2683         if (!cret)
2684                 return -1;
2685
2686         fseek(fd, 0L, SEEK_END);
2687         bytes = ftell(fd);
2688         rewind(fd);
2689
2690         if (progress_gauge_callback)
2691                 progress_gauge_callback(ipc, 0, bytes);
2692
2693         while (offset < bytes) {
2694                 size_t to_write;
2695
2696                 /* Read some data in */
2697                 to_write = fread(buf, 1, 4096, fd);
2698                 if (!to_write) {
2699                         if (feof(fd) || ferror(fd))
2700                                 break;
2701                 }
2702                 sprintf(aaa, "WRIT %d", (int) to_write);
2703                 CtdlIPC_putline(ipc, aaa);
2704                 CtdlIPC_getline(ipc, aaa);
2705                 strcpy(cret, &aaa[4]);
2706                 ret = atoi(aaa);
2707                 if (aaa[0] == '7') {
2708                         to_write = extract_long(&aaa[4], 0);
2709
2710                         serv_write(ipc, buf, to_write);
2711                         offset += to_write;
2712                         if (progress_gauge_callback)
2713                                 progress_gauge_callback(ipc, offset, bytes);
2714                         /* Detect short reads and back up if needed */
2715                         /* offset will never be negative anyway */
2716                         fseek(fd, (signed) offset, SEEK_SET);
2717                 } else {
2718                         break;
2719                 }
2720         }
2721         if (progress_gauge_callback)
2722                 progress_gauge_callback(ipc, 1, 1);
2723         ferr = ferror(fd);
2724         fclose(fd);
2725         return (!ferr ? ret : -2);
2726 }
2727
2728
2729 /*
2730  * Generic command method.  This method should handle any server command
2731  * except for CHAT.  It takes the following arguments:
2732  *
2733  * ipc                  The server to speak with
2734  * command              Preformatted command to send to server
2735  * to_send              A text or binary file to send to server
2736  *                      (only sent if server requests it)
2737  * bytes_to_send        The number of bytes in to_send (required if
2738  *                      sending binary, optional if sending listing)
2739  * to_receive           Pointer to a NULL pointer, if the server
2740  *                      sends text or binary we will allocate memory
2741  *                      for the file and stuff it here
2742  * bytes_to_receive     If a file is received, we will store its
2743  *                      byte count here
2744  * proto_response       The protocol response.  Caller must provide
2745  *                      this buffer and ensure that it is at least
2746  *                      128 bytes in length.
2747  *
2748  * This function returns a number equal to the protocol response number,
2749  * -1 if an internal error occurred, -2 if caller provided bad values,
2750  * or 0 - the protocol response number if bad values were found during
2751  * the protocol exchange.
2752  * It stores the protocol response string (minus the number) in 
2753  * protocol_response as described above.  Some commands send additional
2754  * data in this string.
2755  */
2756 int CtdlIPCGenericCommand(CtdlIPC * ipc,
2757                           const char *command, const char *to_send,
2758                           size_t bytes_to_send, char **to_receive, size_t * bytes_to_receive, char *proto_response)
2759 {
2760         char buf[SIZ];
2761         int ret;
2762
2763         if (!command)
2764                 return -2;
2765         if (!proto_response)
2766                 return -2;
2767
2768         CtdlIPC_lock(ipc);
2769         CtdlIPC_putline(ipc, command);
2770         while (1) {
2771                 CtdlIPC_getline(ipc, proto_response);
2772                 if (proto_response[3] == '*')
2773                         instant_msgs = 1;
2774                 ret = atoi(proto_response);
2775                 strcpy(proto_response, &proto_response[4]);
2776                 switch (ret / 100) {
2777                 default:        /* Unknown, punt */
2778                 case 2: /* OK */
2779                 case 3: /* MORE_DATA */
2780                 case 5: /* ERROR */
2781                         /* Don't need to do anything */
2782                         break;
2783                 case 1: /* LISTING_FOLLOWS */
2784                         if (to_receive && !*to_receive && bytes_to_receive) {
2785                                 *to_receive = CtdlIPCReadListing(ipc, NULL);
2786                         } else {        /* Drain */
2787                                 while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2788                                 ret = -ret;
2789                         }
2790                         break;
2791                 case 4: /* SEND_LISTING */
2792                         if (to_send) {
2793                                 CtdlIPCSendListing(ipc, to_send);
2794                         } else {
2795                                 /* No listing given, fake it */
2796                                 CtdlIPC_putline(ipc, "000");
2797                                 ret = -ret;
2798                         }
2799                         break;
2800                 case 6: /* BINARY_FOLLOWS */
2801                         if (to_receive && !*to_receive && bytes_to_receive) {
2802                                 *bytes_to_receive = extract_long(proto_response, 0);
2803                                 *to_receive = (char *)
2804                                     malloc((size_t) * bytes_to_receive);
2805                                 if (!*to_receive) {
2806                                         ret = -1;
2807                                 } else {
2808                                         serv_read(ipc, *to_receive, *bytes_to_receive);
2809                                 }
2810                         } else {
2811                                 /* Drain */
2812                                 size_t drain;
2813
2814                                 drain = extract_long(proto_response, 0);
2815                                 while (drain > SIZ) {
2816                                         serv_read(ipc, buf, SIZ);
2817                                         drain -= SIZ;
2818                                 }
2819                                 serv_read(ipc, buf, drain);
2820                                 ret = -ret;
2821                         }
2822                         break;
2823                 case 7: /* SEND_BINARY */
2824                         if (to_send && bytes_to_send) {
2825                                 serv_write(ipc, to_send, bytes_to_send);
2826                         } else if (bytes_to_send) {
2827                                 /* Fake it, send nulls */
2828                                 size_t fake;
2829
2830                                 fake = bytes_to_send;
2831                                 memset(buf, '\0', SIZ);
2832                                 while (fake > SIZ) {
2833                                         serv_write(ipc, buf, SIZ);
2834                                         fake -= SIZ;
2835                                 }
2836                                 serv_write(ipc, buf, fake);
2837                                 ret = -ret;
2838                         }       /* else who knows?  DANGER WILL ROBINSON */
2839                         break;
2840                 case 8: /* START_CHAT_MODE */
2841                         if (!strncasecmp(command, "CHAT", 4)) {
2842                                 /* Don't call chatmode with generic! */
2843                                 CtdlIPC_putline(ipc, "/quit");
2844                                 ret = -ret;
2845                         } else {
2846                                 /* In this mode we send then receive listing */
2847                                 if (to_send) {
2848                                         CtdlIPCSendListing(ipc, to_send);
2849                                 } else {
2850                                         /* No listing given, fake it */
2851                                         CtdlIPC_putline(ipc, "000");
2852                                         ret = -ret;
2853                                 }
2854                                 if (to_receive && !*to_receive && bytes_to_receive) {
2855                                         *to_receive = CtdlIPCReadListing(ipc, NULL);
2856                                 } else {        /* Drain */
2857                                         while (CtdlIPC_getline(ipc, buf), strcmp(buf, "000"));
2858                                         ret = -ret;
2859                                 }
2860                         }
2861                         break;
2862                 case 9: /* ASYNC_MSG */
2863                         /* CtdlIPCDoAsync(ret, proto_response); */
2864                         free(CtdlIPCReadListing(ipc, NULL));    /* STUB FIXME */
2865                         break;
2866                 }
2867                 if (ret / 100 != 9)
2868                         break;
2869         }
2870         CtdlIPC_unlock(ipc);
2871         return ret;
2872 }
2873
2874
2875 /*
2876  * Connect to a Citadel on a remote host using a TCP/IP socket
2877  */
2878 static int tcp_connectsock(char *host, char *service)
2879 {
2880         struct in6_addr serveraddr;
2881         struct addrinfo hints;
2882         struct addrinfo *res = NULL;
2883         struct addrinfo *ai = NULL;
2884         int rc = (-1);
2885         int sock = (-1);
2886
2887         if ((host == NULL) || IsEmptyStr(host)) {
2888                 service = DEFAULT_HOST;
2889         }
2890         if ((service == NULL) || IsEmptyStr(service)) {
2891                 service = DEFAULT_PORT;
2892         }
2893
2894         memset(&hints, 0x00, sizeof(hints));
2895         hints.ai_flags = AI_NUMERICSERV;
2896         hints.ai_family = AF_UNSPEC;
2897         hints.ai_socktype = SOCK_STREAM;
2898
2899         /*
2900          * Handle numeric IPv4 and IPv6 addresses
2901          */
2902         rc = inet_pton(AF_INET, host, &serveraddr);
2903         if (rc == 1) {          /* dotted quad */
2904                 hints.ai_family = AF_INET;
2905                 hints.ai_flags |= AI_NUMERICHOST;
2906         } else {
2907                 rc = inet_pton(AF_INET6, host, &serveraddr);
2908                 if (rc == 1) {  /* IPv6 address */
2909                         hints.ai_family = AF_INET6;
2910                         hints.ai_flags |= AI_NUMERICHOST;
2911                 }
2912         }
2913
2914         /* Begin the connection process */
2915
2916         rc = getaddrinfo(host, service, &hints, &res);
2917         if (rc != 0) {
2918                 return (-1);
2919         }
2920
2921         /*
2922          * Try all available addresses until we connect to one or until we run out.
2923          */
2924         for (ai = res; ai != NULL; ai = ai->ai_next) {
2925                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2926                 if (sock < 0)
2927                         return (-1);
2928
2929                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
2930                 if (rc >= 0) {
2931                         return (sock);  /* Connected! */
2932                 } else {
2933                         close(sock);    /* Failed.  Close the socket to avoid fd leak! */
2934                 }
2935         }
2936
2937         return (-1);
2938 }
2939
2940
2941
2942
2943
2944 /*
2945  * Connect to a Citadel on the local host using a unix domain socket
2946  */
2947 static int uds_connectsock(int *isLocal, char *sockpath)
2948 {
2949         struct sockaddr_un addr;
2950         int s;
2951
2952         memset(&addr, 0, sizeof(addr));
2953         addr.sun_family = AF_UNIX;
2954         safestrncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
2955
2956         s = socket(AF_UNIX, SOCK_STREAM, 0);
2957         if (s < 0) {
2958                 return -1;
2959         }
2960
2961         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
2962                 close(s);
2963                 return -1;
2964         }
2965
2966         *isLocal = 1;
2967         return s;
2968 }
2969
2970
2971 /*
2972  * input binary data from socket
2973  */
2974 static void serv_read(CtdlIPC * ipc, char *buf, unsigned int bytes)
2975 {
2976         unsigned int len, rlen;
2977
2978 #if defined(HAVE_OPENSSL)
2979         if (ipc->ssl) {
2980                 serv_read_ssl(ipc, buf, bytes);
2981                 return;
2982         }
2983 #endif
2984         len = 0;
2985         while (len < bytes) {
2986                 rlen = read(ipc->sock, &buf[len], bytes - len);
2987                 if (rlen < 1) {
2988                         connection_died(ipc, 0);
2989                         return;
2990                 }
2991                 len += rlen;
2992         }
2993 }
2994
2995
2996 /*
2997  * send binary to server
2998  */
2999 void serv_write(CtdlIPC * ipc, const char *buf, unsigned int nbytes)
3000 {
3001         unsigned int bytes_written = 0;
3002         int retval;
3003
3004 #if defined(HAVE_OPENSSL)
3005         if (ipc->ssl) {
3006                 serv_write_ssl(ipc, buf, nbytes);
3007                 return;
3008         }
3009 #endif
3010         while (bytes_written < nbytes) {
3011                 retval = write(ipc->sock, &buf[bytes_written], nbytes - bytes_written);
3012                 if (retval < 1) {
3013                         connection_died(ipc, 0);
3014                         return;
3015                 }
3016                 bytes_written += retval;
3017         }
3018 }
3019
3020
3021 #ifdef HAVE_OPENSSL
3022 /*
3023  * input binary data from encrypted connection
3024  */
3025 static void serv_read_ssl(CtdlIPC * ipc, char *buf, unsigned int bytes)
3026 {
3027         int len, rlen;
3028         char junk[1];
3029
3030         len = 0;
3031         while (len < bytes) {
3032                 if (SSL_want_read(ipc->ssl)) {
3033                         if ((SSL_write(ipc->ssl, junk, 0)) < 1) {
3034                                 error_printf("SSL_write in serv_read:\n");
3035                                 ERR_print_errors_fp(stderr);
3036                         }
3037                 }
3038                 rlen = SSL_read(ipc->ssl, &buf[len], bytes - len);
3039                 if (rlen < 1) {
3040                         long errval;
3041
3042                         errval = SSL_get_error(ipc->ssl, rlen);
3043                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
3044                                 sleep(1);
3045                                 continue;
3046                         }
3047 /***
3048  Not sure why we'd want to handle these error codes any differently,
3049  but this definitely isn't the way to handle them.  Someone must have
3050  naively assumed that we could fall back to unencrypted communications,
3051  but all it does is just recursively blow the stack.
3052                         if (errval == SSL_ERROR_ZERO_RETURN ||
3053                                         errval == SSL_ERROR_SSL) {
3054                                 serv_read(ipc, &buf[len], bytes - len);
3055                                 return;
3056                         }
3057  ***/
3058                         error_printf("SSL_read in serv_read: %s\n", ERR_reason_error_string(ERR_peek_error()));
3059                         connection_died(ipc, 1);
3060                         return;
3061                 }
3062                 len += rlen;
3063         }
3064 }
3065
3066
3067 /*
3068  * send binary to server encrypted
3069  */
3070 static void serv_write_ssl(CtdlIPC * ipc, const char *buf, unsigned int nbytes)
3071 {
3072         unsigned int bytes_written = 0;
3073         int retval;
3074         char junk[1];
3075
3076         while (bytes_written < nbytes) {
3077                 if (SSL_want_write(ipc->ssl)) {
3078                         if ((SSL_read(ipc->ssl, junk, 0)) < 1) {
3079                                 error_printf("SSL_read in serv_write:\n");
3080                                 ERR_print_errors_fp(stderr);
3081                         }
3082                 }
3083                 retval = SSL_write(ipc->ssl, &buf[bytes_written], nbytes - bytes_written);
3084                 if (retval < 1) {
3085                         long errval;
3086
3087                         errval = SSL_get_error(ipc->ssl, retval);
3088                         if (errval == SSL_ERROR_WANT_READ || errval == SSL_ERROR_WANT_WRITE) {
3089                                 sleep(1);
3090                                 continue;
3091                         }
3092                         if (errval == SSL_ERROR_ZERO_RETURN || errval == SSL_ERROR_SSL) {
3093                                 serv_write(ipc, &buf[bytes_written], nbytes - bytes_written);
3094                                 return;
3095                         }
3096                         error_printf("SSL_write in serv_write: %s\n", ERR_reason_error_string(ERR_peek_error()));
3097                         connection_died(ipc, 1);
3098                         return;
3099                 }
3100                 bytes_written += retval;
3101         }
3102 }
3103
3104
3105
3106
3107 static void CtdlIPC_init_OpenSSL(void)
3108 {
3109         int a;
3110         const SSL_METHOD *ssl_method;
3111         DH *dh;
3112
3113         /* already done init */
3114         if (ssl_ctx) {
3115                 return;
3116         }
3117
3118         /* Get started */
3119         a = 0;
3120         ssl_ctx = NULL;
3121         dh = NULL;
3122         SSL_load_error_strings();
3123         SSLeay_add_ssl_algorithms();
3124
3125         /* Set up the SSL context in which we will oeprate */
3126         ssl_method = SSLv23_client_method();
3127         ssl_ctx = SSL_CTX_new(ssl_method);
3128         if (!ssl_ctx) {
3129                 error_printf("SSL_CTX_new failed: %s\n", ERR_reason_error_string(ERR_get_error()));
3130                 return;
3131         }
3132         /* Any reasonable cipher we can get */
3133         if (!(SSL_CTX_set_cipher_list(ssl_ctx, CIT_CIPHERS))) {
3134                 error_printf("No ciphers available for encryption\n");
3135                 return;
3136         }
3137         SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
3138
3139         /* Load DH parameters into the context */
3140         dh = DH_new();
3141         if (!dh) {
3142                 error_printf("Can't allocate a DH object: %s\n", ERR_reason_error_string(ERR_get_error()));
3143                 return;
3144         }
3145
3146         if (!(DH_generate_parameters_ex(dh, 128, DH_GENERATOR_2, 0))) {
3147                 error_printf("Can't generate DH parameters: %s\n", ERR_reason_error_string(ERR_get_error()));
3148                 DH_free(dh);
3149                 return;
3150         }
3151
3152         SSL_CTX_set_tmp_dh(ssl_ctx, dh);
3153         DH_free(dh);
3154 }
3155
3156 #endif                          /* HAVE_OPENSSL */
3157
3158
3159 int ReadNetworkChunk(CtdlIPC * ipc)
3160 {
3161         fd_set read_fd;
3162         int ret = 0;
3163         int err = 0;
3164         struct timeval tv;
3165         size_t n;
3166
3167         tv.tv_sec = 1;
3168         tv.tv_usec = 1000;
3169         /*tries = 0; */
3170         n = 0;
3171         while (1) {
3172                 errno = 0;
3173                 FD_ZERO(&read_fd);
3174                 FD_SET(ipc->sock, &read_fd);
3175                 ret = select(ipc->sock + 1, &read_fd, NULL, NULL, &tv);
3176
3177                 if (ret > 0) {
3178
3179                         *(ipc->BufPtr) = '\0';
3180                         n = recv(ipc->sock, ipc->BufPtr, ipc->BufSize - (ipc->BufPtr - ipc->Buf) - 1, 0);
3181                         if (n > 0) {
3182                                 ipc->BufPtr[n] = '\0';
3183                                 ipc->BufUsed += n;
3184                                 return n;
3185                         } else
3186                                 return n;
3187                 } else if (ret < 0) {
3188                         if (!(errno == EINTR || errno == EAGAIN))
3189                                 error_printf("\nselect failed: %d %s\n", err, strerror(err));
3190                         return -1;
3191                 }               /*
3192                                    else {
3193                                    tries ++;
3194                                    if (tries >= 10)
3195                                    n = read(ipc->sock, ipc->BufPtr, ipc->BufSize  - (ipc->BufPtr - ipc->Buf) - 1);
3196                                    if (n > 0) {
3197                                    ipc->BufPtr[n]='\0';
3198                                    ipc->BufUsed += n;
3199                                    return n;
3200                                    }
3201                                    else {
3202                                    connection_died(ipc, 0);
3203                                    return -1;
3204                                    }
3205                                    } */
3206         }
3207 }
3208
3209 /*
3210  * input string from socket - implemented in terms of serv_read()
3211  */
3212 #ifdef CHUNKED_READ
3213
3214 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf)
3215 {
3216         int i, ntries;
3217         char *aptr, *bptr, *aeptr, *beptr;
3218
3219 //      error_printf("---\n");
3220
3221         beptr = buf + SIZ;
3222 #if defined(HAVE_OPENSSL)
3223         if (ipc->ssl) {
3224
3225                 /* Read one character at a time. */
3226                 for (i = 0;; i++) {
3227                         serv_read(ipc, &buf[i], 1);
3228                         if (buf[i] == '\n' || i == (SIZ - 1))
3229                                 break;
3230                 }
3231
3232                 /* If we got a long line, discard characters until the newline. */
3233                 if (i == (SIZ - 1))
3234                         while (buf[i] != '\n')
3235                                 serv_read(ipc, &buf[i], 1);
3236
3237                 /* Strip the trailing newline (and carriage return, if present) */
3238                 if (i >= 0 && buf[i] == 10)
3239                         buf[i--] = 0;
3240                 if (i >= 0 && buf[i] == 13)
3241                         buf[i--] = 0;
3242         } else
3243 #endif
3244         {
3245                 if (ipc->Buf == NULL) {
3246                         ipc->BufSize = SIZ;
3247                         ipc->Buf = (char *) malloc(ipc->BufSize + 10);
3248                         *(ipc->Buf) = '\0';
3249                         ipc->BufPtr = ipc->Buf;
3250                 }
3251
3252                 ntries = 0;
3253 //              while ((ipc->BufUsed == 0)||(ntries++ > 10))
3254                 if (ipc->BufUsed == 0)
3255                         ReadNetworkChunk(ipc);
3256
3257 ////            if (ipc->BufUsed != 0) while (1)
3258                 bptr = buf;
3259
3260                 while (1) {
3261                         aptr = ipc->BufPtr;
3262                         aeptr = ipc->Buf + ipc->BufSize;
3263                         while ((aptr < aeptr) && (bptr < beptr) && (*aptr != '\0') && (*aptr != '\n'))
3264                                 *(bptr++) = *(aptr++);
3265                         if ((*aptr == '\n') && (aptr < aeptr)) {
3266                                 /* Terminate it right, remove the line breaks */
3267                                 while ((aptr < aeptr) && ((*aptr == '\n') || (*aptr == '\r')))
3268                                         aptr++;
3269                                 while ((aptr < aeptr) && (*(aptr + 1) == '\0'))
3270                                         aptr++;
3271                                 *(bptr++) = '\0';
3272 //                              fprintf(stderr, "parsing %d %d %d - %d %d %d %s\n", ipc->BufPtr - ipc->Buf, aptr - ipc->BufPtr, ipc->BufUsed , *aptr, *(aptr-1), *(aptr+1), buf);
3273                                 if ((bptr > buf + 1) && (*(bptr - 1) == '\r'))
3274                                         *(--bptr) = '\0';
3275
3276                                 /* is there more in the buffer we need to read later? */
3277                                 if (ipc->Buf + ipc->BufUsed > aptr) {
3278                                         ipc->BufPtr = aptr;
3279                                 } else {
3280                                         ipc->BufUsed = 0;
3281                                         ipc->BufPtr = ipc->Buf;
3282                                 }
3283 //                              error_printf("----bla6\n");
3284                                 return;
3285
3286                         } /* should we move our read stuf to the bufferstart so we have more space at the end? */
3287                         else if ((ipc->BufPtr != ipc->Buf) && (ipc->BufUsed > (ipc->BufSize - (ipc->BufSize / 4)))) {
3288                                 size_t NewBufSize = ipc->BufSize * 2;
3289                                 int delta = (ipc->BufPtr - ipc->Buf);
3290                                 char *NewBuf;
3291
3292                                 /* if the line would end after our buffer, we should use a bigger buffer. */
3293                                 NewBuf = (char *) malloc(NewBufSize + 10);
3294                                 memcpy(NewBuf, ipc->BufPtr, ipc->BufUsed - delta);
3295                                 free(ipc->Buf);
3296                                 ipc->Buf = ipc->BufPtr = NewBuf;
3297                                 ipc->BufUsed -= delta;
3298                                 ipc->BufSize = NewBufSize;
3299                         }
3300                         if (ReadNetworkChunk(ipc) < 0) {
3301 //                              error_printf("----bla\n");
3302                                 return;
3303                         }
3304                 }
3305 ///             error_printf("----bl45761%s\nipc->BufUsed");
3306         }
3307 //      error_printf("----bla1\n");
3308 }
3309
3310 #else                           /* CHUNKED_READ */
3311
3312 static void CtdlIPC_getline(CtdlIPC * ipc, char *buf)
3313 {
3314         int i;
3315
3316         /* Read one character at a time. */
3317         for (i = 0;; i++) {
3318                 serv_read(ipc, &buf[i], 1);
3319                 if (buf[i] == '\n' || i == (SIZ - 1))
3320                         break;
3321         }
3322
3323         /* If we got a long line, discard characters until the newline. */
3324         if (i == (SIZ - 1))
3325                 while (buf[i] != '\n')
3326                         serv_read(ipc, &buf[i], 1);
3327
3328         /* Strip the trailing newline (and carriage return, if present) */
3329         if (i >= 0 && buf[i] == 10)
3330                 buf[i--] = 0;
3331         if (i >= 0 && buf[i] == 13)
3332                 buf[i--] = 0;
3333 }
3334
3335
3336 #endif                          /* CHUNKED_READ */
3337
3338
3339 void CtdlIPC_chat_recv(CtdlIPC * ipc, char *buf)
3340 {
3341         CtdlIPC_getline(ipc, buf);
3342 }
3343
3344 /*
3345  * send line to server - implemented in terms of serv_write()
3346  */
3347 static void CtdlIPC_putline(CtdlIPC * ipc, const char *buf)
3348 {
3349         char *cmd = NULL;
3350         int len;
3351
3352         len = strlen(buf);
3353         cmd = malloc(len + 2);
3354         if (!cmd) {
3355                 /* This requires no extra memory */
3356                 serv_write(ipc, buf, len);
3357                 serv_write(ipc, "\n", 1);
3358         } else {
3359                 /* This is network-optimized */
3360                 strncpy(cmd, buf, len);
3361                 strcpy(cmd + len, "\n");
3362                 serv_write(ipc, cmd, len + 1);
3363                 free(cmd);
3364         }
3365
3366         ipc->last_command_sent = time(NULL);
3367 }
3368
3369 void CtdlIPC_chat_send(CtdlIPC * ipc, const char *buf)
3370 {
3371         CtdlIPC_putline(ipc, buf);
3372 }
3373
3374
3375 /*
3376  * attach to server
3377  */
3378 CtdlIPC *CtdlIPC_new(int argc, char **argv, char *hostbuf, char *portbuf)
3379 {
3380         int a;
3381         char cithost[SIZ];
3382         char citport[SIZ];
3383         char sockpath[SIZ];
3384         CtdlIPC *ipc;
3385
3386         ipc = malloc(sizeof(struct _CtdlIPC));
3387         if (!ipc) {
3388                 return 0;
3389         }
3390 #if defined(HAVE_OPENSSL)
3391         ipc->ssl = NULL;
3392         CtdlIPC_init_OpenSSL();
3393 #endif
3394         ipc->sock = -1;         /* Not connected */
3395         ipc->isLocal = 0;       /* Not local, of course! */
3396         ipc->downloading = 0;
3397         ipc->uploading = 0;
3398         ipc->last_command_sent = 0L;
3399         ipc->network_status_cb = NULL;
3400         ipc->Buf = NULL;
3401         ipc->BufUsed = 0;
3402         ipc->BufPtr = NULL;
3403
3404         strcpy(cithost, DEFAULT_HOST);  /* default host */
3405         strcpy(citport, DEFAULT_PORT);  /* default port */
3406
3407         /* Allow caller to supply our values */
3408         if (hostbuf && strlen(hostbuf) > 0) {
3409                 strcpy(cithost, hostbuf);
3410         }
3411         if (portbuf && strlen(portbuf) > 0) {
3412                 strcpy(citport, portbuf);
3413         }
3414
3415         /* Read host/port from command line if present */
3416         for (a = 0; a < argc; ++a) {
3417                 if (a == 0) {
3418                         /* do nothing */
3419                 } else if (a == 1) {
3420                         strcpy(cithost, argv[a]);
3421                 } else if (a == 2) {
3422                         strcpy(citport, argv[a]);
3423                 } else {
3424                         error_printf("%s: usage: ", argv[0]);
3425                         error_printf("%s [host] [port] ", argv[0]);
3426                         free(ipc);
3427                         errno = EINVAL;
3428                         return 0;
3429                 }
3430         }
3431
3432         if ((!strcmp(cithost, "localhost")) || (!strcmp(cithost, "127.0.0.1"))) {
3433                 ipc->isLocal = 1;
3434         }
3435
3436         /* If we're using a unix domain socket we can do a bunch of stuff */
3437         if (!strcmp(cithost, UDS)) {
3438                 if (!strcasecmp(citport, DEFAULT_PORT)) {
3439                         snprintf(sockpath, sizeof sockpath, "%s", file_citadel_socket);
3440                 } else {
3441                         snprintf(sockpath, sizeof sockpath, "%s/%s", citport, "citadel.socket");
3442                 }
3443                 printf("[%s]\n", sockpath);
3444                 ipc->sock = uds_connectsock(&(ipc->isLocal), sockpath);
3445                 if (ipc->sock == -1) {
3446                         free(ipc);
3447                         return 0;
3448                 }
3449                 if (hostbuf != NULL)
3450                         strcpy(hostbuf, cithost);
3451                 if (portbuf != NULL)
3452                         strcpy(portbuf, sockpath);
3453                 strcpy(ipc->ip_hostname, "");
3454                 strcpy(ipc->ip_address, "");
3455                 return ipc;
3456         }
3457
3458         printf("[%s:%s]\n", cithost, citport);
3459         ipc->sock = tcp_connectsock(cithost, citport);
3460         if (ipc->sock == -1) {
3461                 free(ipc);
3462                 return 0;
3463         }
3464
3465
3466         /* Learn the actual network identity of the host to which we are connected */
3467
3468         struct sockaddr_in6 clientaddr;
3469         unsigned int addrlen = sizeof(clientaddr);
3470
3471         ipc->ip_hostname[0] = 0;
3472         ipc->ip_address[0] = 0;
3473
3474         getpeername(ipc->sock, (struct sockaddr *) &clientaddr, &addrlen);
3475         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_hostname, sizeof ipc->ip_hostname, NULL, 0, 0);
3476         getnameinfo((struct sockaddr *) &clientaddr, addrlen, ipc->ip_address, sizeof ipc->ip_address, NULL, 0, NI_NUMERICHOST);
3477
3478         /* stuff other things elsewhere */
3479
3480         if (hostbuf != NULL)
3481                 strcpy(hostbuf, cithost);
3482         if (portbuf != NULL)
3483                 strcpy(portbuf, citport);
3484         return ipc;
3485 }
3486
3487
3488 /*
3489  * Disconnect and delete the IPC class (destructor)
3490  */