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